Added Dungeon Manager.
This commit is contained in:
parent
25090a5316
commit
e74a209bb8
@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
class GameDataManager {
|
||||
constructor() {
|
||||
this.items = new Map();
|
||||
@ -7,6 +5,7 @@ class GameDataManager {
|
||||
this.skills = new Map();
|
||||
this.dungeons = new Map();
|
||||
this.hostiles = new Map();
|
||||
this.rooms = new Map();
|
||||
this.translations = {};
|
||||
this.manifest = {};
|
||||
this.currentLang = localStorage.getItem("selected_lang") || "en_US";
|
||||
@ -29,6 +28,9 @@ class GameDataManager {
|
||||
if (Array.isArray(data.skills)) {
|
||||
data.skills.forEach((s) => this.skills.set(s.id, s));
|
||||
}
|
||||
if (Array.isArray(data.rooms)) {
|
||||
data.rooms.forEach((r) => this.rooms.set(r.id, r));
|
||||
}
|
||||
|
||||
if (data.languages) {
|
||||
this.translations = data.languages;
|
||||
@ -122,6 +124,10 @@ class GameDataManager {
|
||||
};
|
||||
}
|
||||
|
||||
getEnemy(id) {
|
||||
return this.getHostile(id);
|
||||
}
|
||||
|
||||
getItem(id) {
|
||||
const item = this.items.get(id);
|
||||
|
||||
@ -176,6 +182,16 @@ class GameDataManager {
|
||||
};
|
||||
}
|
||||
|
||||
getRoom(id) {
|
||||
const room = this.rooms.get(id);
|
||||
if (!room) return null;
|
||||
return {
|
||||
...room,
|
||||
displayName: this.t(room.displayName),
|
||||
description: this.t(room.description),
|
||||
};
|
||||
}
|
||||
|
||||
setLanguage(langCode) {
|
||||
if (this.translations[langCode]) {
|
||||
this.currentLang = langCode;
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
font-family: "Space Mono", monospace;
|
||||
color: #e0e6ed;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.dungeon-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -18,6 +18,7 @@
|
||||
border-bottom: 1px solid rgba(0, 212, 255, 0.3);
|
||||
padding-bottom: 15px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dungeon-header::after {
|
||||
@ -73,19 +74,18 @@
|
||||
animation: blink 1.5s infinite;
|
||||
}
|
||||
|
||||
/* LAYOUT */
|
||||
.battle-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr;
|
||||
flex: 1;
|
||||
gap: 30px;
|
||||
min-height: 0; /* Важливо для overflow лога */
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ENEMY CARD */
|
||||
.enemy-display {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.enemy-card {
|
||||
@ -152,7 +152,6 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* HP BAR MINI */
|
||||
.enemy-hp-container {
|
||||
width: 60%;
|
||||
margin-bottom: 30px;
|
||||
@ -187,12 +186,14 @@
|
||||
color: rgba(160, 172, 186, 0.7);
|
||||
}
|
||||
|
||||
/* COMBAT LOG */
|
||||
.combat-log-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(26, 38, 56, 0.8);
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
@ -203,6 +204,7 @@
|
||||
letter-spacing: 1px;
|
||||
color: #00d4ff;
|
||||
border-bottom: 1px solid rgba(26, 38, 56, 0.8);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.combat-log {
|
||||
@ -215,12 +217,14 @@
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #1a2638 transparent;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
line-height: 1.4;
|
||||
color: #a0acba;
|
||||
animation: slideIn 0.2s ease-out;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.log-arrow {
|
||||
@ -229,11 +233,11 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* CONTROLS */
|
||||
.dungeon-controls {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
height: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ctrl-btn {
|
||||
@ -277,7 +281,6 @@
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* UTILS */
|
||||
.empty-room {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@ -325,19 +328,17 @@
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Вирівнювання футера картки */
|
||||
.enemy-info-footer {
|
||||
display: flex;
|
||||
justify-content: space-between; /* Розносимо LVL та ID по боках */
|
||||
width: 80%; /* Щоб не були впритул до країв */
|
||||
margin-top: auto; /* Притискаємо до низу */
|
||||
justify-content: space-between;
|
||||
width: 80%;
|
||||
margin-top: auto;
|
||||
padding-bottom: 20px;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Ефект трясіння при атаці */
|
||||
.enemy-card.taking-damage {
|
||||
animation: shake 0.2s ease-in-out;
|
||||
border-color: #ffffff;
|
||||
@ -362,7 +363,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Покращення читабельності лога */
|
||||
.combat-log-wrapper {
|
||||
min-width: 300px; /* Щоб лог не стискався занадто сильно */
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.environment-panel {
|
||||
background: rgba(0, 20, 40, 0.6);
|
||||
border: 1px solid rgba(0, 255, 255, 0.2);
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.env-header {
|
||||
font-size: 0.65rem;
|
||||
color: #00ffff;
|
||||
opacity: 0.7;
|
||||
letter-spacing: 1.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.env-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.env-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: rgba(0, 255, 255, 0.1);
|
||||
border: 1px solid rgba(0, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #00ffff;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.env-details .env-id {
|
||||
font-family: "Orbitron", sans-serif;
|
||||
font-size: 0.8rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.env-details .env-type {
|
||||
font-size: 0.6rem;
|
||||
color: #888;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.card-id {
|
||||
opacity: 0.4;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import "./DungeonScreen.css";
|
||||
|
||||
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);
|
||||
@ -14,31 +15,43 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
|
||||
const logEndRef = useRef(null);
|
||||
|
||||
// Отримуємо дані про ворога та данж через GameDataManager
|
||||
const rawEnemyId = roomData?.hostiles?.[0] || null;
|
||||
const enemyData = rawEnemyId ? GameDataManager.getEnemy(rawEnemyId) : 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); // Скидаємо HP для нового ворога
|
||||
setEnemyHp(null);
|
||||
addLog(`--- ENTERING SECTOR ${data.roomIndex + 1} ---`);
|
||||
});
|
||||
|
||||
// Слухаємо результати бою від сервера
|
||||
socket.on("dungeon:combat_result", (data) => {
|
||||
if (data.message) addLog(data.message);
|
||||
if (data.enemyHp !== undefined) setEnemyHp(data.enemyHp);
|
||||
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.");
|
||||
@ -49,7 +62,7 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
socket.off("dungeon:room_update");
|
||||
socket.off("dungeon:combat_result");
|
||||
};
|
||||
}, [socket]);
|
||||
}, [socket, currentEnemy]);
|
||||
|
||||
const addLog = (text) => {
|
||||
const time = new Date().toLocaleTimeString([], {
|
||||
@ -62,15 +75,12 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
};
|
||||
|
||||
const handleCombat = () => {
|
||||
if (isEnemyDefeated) return;
|
||||
socket.emit("dungeon:combat_step", { enemyId: rawEnemyId });
|
||||
addLog(
|
||||
`Initiating strike sequence on ${enemyData?.displayName || "Target"}...`,
|
||||
);
|
||||
if (isEnemyDefeated || !currentEnemy) return;
|
||||
socket.emit("dungeon:combat_step", { enemyId: currentEnemy.id });
|
||||
addLog(`Initiating strike sequence...`);
|
||||
};
|
||||
|
||||
const handleLoot = () => {
|
||||
socket.emit("dungeon:get_loot");
|
||||
setIsLooted(true);
|
||||
addLog("Loot encryption bypassed. Resources transferred.");
|
||||
};
|
||||
@ -103,11 +113,34 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
</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" : ""}`}
|
||||
>
|
||||
{enemyData ? (
|
||||
{currentEnemy ? (
|
||||
<div className={`enemy-card ${isEnemyDefeated ? "defeated" : ""}`}>
|
||||
<div className="threat-tag">
|
||||
{isEnemyDefeated ? "SIGNAL_LOST" : "HOSTILE_DETECTED"}
|
||||
@ -117,7 +150,9 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
className={`fas ${isEnemyDefeated ? "fa-skull-crossbones" : "fa-robot"}`}
|
||||
></i>
|
||||
</div>
|
||||
<h3 className="enemy-name">{enemyData.displayName}</h3>
|
||||
<h3 className="enemy-name">
|
||||
{getEnemyDisplayName(currentEnemy)}
|
||||
</h3>
|
||||
<div className="enemy-hp-container">
|
||||
<div className="hp-label">STRUCTURE INTEGRITY</div>
|
||||
<div className="hp-bar-mini">
|
||||
@ -132,8 +167,8 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="enemy-info-footer">
|
||||
<span>LVL: {enemyData.level || 1}</span>
|
||||
<span>ID: {GameDataManager._cleanId(rawEnemyId)}</span>
|
||||
<span>LVL: {currentEnemy.level || 1}</span>
|
||||
<span className="card-id">{currentEnemy.id}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@ -158,7 +193,7 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
</div>
|
||||
|
||||
<div className="dungeon-controls">
|
||||
{!isEnemyDefeated && enemyData && (
|
||||
{!isEnemyDefeated && currentEnemy && (
|
||||
<button className="ctrl-btn combat" onClick={handleCombat}>
|
||||
<i className="fas fa-bolt"></i> ENGAGE
|
||||
</button>
|
||||
@ -170,7 +205,7 @@ const DungeonScreen = ({ session, socket }) => {
|
||||
</button>
|
||||
)}
|
||||
|
||||
{(isLooted || !enemyData) && (
|
||||
{(isLooted || !currentEnemy) && (
|
||||
<button className="ctrl-btn next" onClick={handleNextRoom}>
|
||||
PROCEED <i className="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"rooms": {
|
||||
"id": "original:pirate_ambush_zone",
|
||||
"id": "original:pirate/pirate_ambush_zone",
|
||||
"hostiles": [
|
||||
"original:pirates/scout_drone",
|
||||
"original:pirates/raider_frigate",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"rooms": {
|
||||
"id": "original:pirate_boss_bridge",
|
||||
"id": "original:pirate/pirate_boss_bridge",
|
||||
"hostiles": ["original:pirates/black_mark_cruiser"],
|
||||
"gainXp": 100,
|
||||
"credits": 2500
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"rooms": {
|
||||
"id": "original:pirate_patrol_room",
|
||||
"id": "original:pirate/pirate_patrol_room",
|
||||
"hostiles": [
|
||||
"original:pirates/scout_drone",
|
||||
"original:pirates/scout_drone"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"rooms": {
|
||||
"id": "original:pirate_supply_bay",
|
||||
"id": "original:pirate/pirate_supply_bay",
|
||||
"hostiles": [],
|
||||
"gainXp": 5,
|
||||
"credits": 800,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"rooms": {
|
||||
"id": "original:tutorial/tutorial_boss",
|
||||
"id": "original:tutorial/tutorial_boss_room",
|
||||
"hostiles": ["original:tutorial/tutorial_boss_hostile"],
|
||||
"gainXp": 4,
|
||||
"credits": 200
|
||||
|
||||
86
game-server/src/game/DungeonManager.js
Normal file
86
game-server/src/game/DungeonManager.js
Normal file
@ -0,0 +1,86 @@
|
||||
const DatapackLoader = require("./DatapackLoader");
|
||||
|
||||
class DungeonManager {
|
||||
constructor() {
|
||||
this.activeSessions = new Map();
|
||||
}
|
||||
|
||||
startDungeon(playerId, dungeonId) {
|
||||
const dungeon = DatapackLoader.getDungeon(dungeonId);
|
||||
if (!dungeon || !dungeon.rooms?.length) return null;
|
||||
|
||||
const session = {
|
||||
dungeonId,
|
||||
currentRoomIndex: 0,
|
||||
isFinished: false,
|
||||
currentEnemyHp: undefined,
|
||||
rewards: { xp: 0, credits: 0, items: [] },
|
||||
};
|
||||
|
||||
this.activeSessions.set(playerId, session);
|
||||
return this.getCurrentRoomData(playerId);
|
||||
}
|
||||
|
||||
getCurrentRoomData(playerId) {
|
||||
const session = this.activeSessions.get(playerId);
|
||||
if (!session) return null;
|
||||
|
||||
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
|
||||
const roomRef = dungeon.rooms[session.currentRoomIndex];
|
||||
const roomData = DatapackLoader.getRoom(roomRef.id);
|
||||
if (!roomData) return null;
|
||||
|
||||
const hostiles = (roomData.hostiles || [])
|
||||
.map((hId) => {
|
||||
const hostile = DatapackLoader.getEnemy(hId);
|
||||
return hostile ? { ...hostile } : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return {
|
||||
roomIndex: session.currentRoomIndex,
|
||||
totalRooms: dungeon.rooms.length,
|
||||
config: roomData,
|
||||
hostiles,
|
||||
};
|
||||
}
|
||||
|
||||
moveToNextRoom(playerId) {
|
||||
const session = this.activeSessions.get(playerId);
|
||||
if (!session || session.isFinished) return null;
|
||||
|
||||
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
|
||||
const roomRef = dungeon.rooms[session.currentRoomIndex];
|
||||
const currentRoom = DatapackLoader.getRoom(roomRef.id);
|
||||
|
||||
if (currentRoom) {
|
||||
session.rewards.xp += currentRoom.gainXp || 0;
|
||||
session.rewards.credits += currentRoom.credits || 0;
|
||||
|
||||
if (currentRoom.loot && Array.isArray(currentRoom.loot)) {
|
||||
currentRoom.loot.forEach((item) => {
|
||||
session.rewards.items.push({ ...item });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
session.currentEnemyHp = undefined;
|
||||
|
||||
if (session.currentRoomIndex < dungeon.rooms.length - 1) {
|
||||
session.currentRoomIndex++;
|
||||
return this.getCurrentRoomData(playerId);
|
||||
}
|
||||
|
||||
session.isFinished = true;
|
||||
return {
|
||||
status: "completed",
|
||||
rewards: session.rewards,
|
||||
};
|
||||
}
|
||||
|
||||
leaveDungeon(playerId) {
|
||||
this.activeSessions.delete(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DungeonManager();
|
||||
@ -1,6 +1,6 @@
|
||||
const { Player, Inventory } = require("../../models");
|
||||
const sessionManager = require("../../game/SessionManager");
|
||||
const DatapackLoader = require("../../game/DatapackLoader");
|
||||
const dungeonManager = require("../../game/DungeonManager");
|
||||
|
||||
module.exports = (io, socket) => {
|
||||
const userId = socket.user?.id;
|
||||
@ -8,80 +8,71 @@ module.exports = (io, socket) => {
|
||||
socket.on("dungeon:start", async ({ dungeonId }) => {
|
||||
try {
|
||||
if (!userId) return;
|
||||
|
||||
const dungeon = DatapackLoader.getDungeon(dungeonId);
|
||||
if (!dungeon) {
|
||||
return socket.emit("error", { message: "Dungeon coordinates invalid" });
|
||||
return socket.emit("error", { message: "Dungeon not found" });
|
||||
}
|
||||
|
||||
const player = await Player.findByPk(userId);
|
||||
if (player.energy < dungeon.energyCost) {
|
||||
const energyCost = dungeon.meta?.energyCost || 0;
|
||||
|
||||
if (player.energy < energyCost) {
|
||||
return socket.emit("error", { message: "Insufficient energy" });
|
||||
}
|
||||
|
||||
await player.decrement("energy", { by: energyCost });
|
||||
|
||||
const firstRoom = dungeonManager.startDungeon(userId, dungeonId);
|
||||
if (!firstRoom) {
|
||||
return socket.emit("error", {
|
||||
message: "Insufficient energy for deployment",
|
||||
message: "Failed to initialize dungeon",
|
||||
});
|
||||
}
|
||||
|
||||
await player.decrement("energy", { by: dungeon.energyCost });
|
||||
|
||||
const dungeonSession = {
|
||||
dungeonId: dungeon.id,
|
||||
currentRoomIndex: 0,
|
||||
isCompleted: false,
|
||||
startTime: Date.now(),
|
||||
rewards: [],
|
||||
};
|
||||
|
||||
sessionManager.setPlayerScene(socket.id, "dungeon", dungeonSession);
|
||||
|
||||
const firstRoomId = dungeon.rooms[0].id;
|
||||
const firstRoomData = DatapackLoader.getRoom(firstRoomId);
|
||||
|
||||
socket.emit("dungeon:started", {
|
||||
dungeonId: dungeon.id,
|
||||
room: firstRoomData,
|
||||
roomIndex: 0,
|
||||
totalRooms: dungeon.rooms.length,
|
||||
remainingEnergy: player.energy - dungeon.energyCost,
|
||||
room: firstRoom.config,
|
||||
hostiles: firstRoom.hostiles,
|
||||
roomIndex: firstRoom.roomIndex,
|
||||
totalRooms: firstRoom.totalRooms,
|
||||
remainingEnergy: player.energy - energyCost,
|
||||
});
|
||||
|
||||
console.log(`[Dungeon] Player ${userId} started ${dungeonId}`);
|
||||
} catch (err) {
|
||||
console.error("Dungeon Start Error:", err);
|
||||
socket.emit("error", { message: "Failed to initiate deployment" });
|
||||
socket.emit("error", { message: "Critical deployment failure" });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("dungeon:combat_step", async (data) => {
|
||||
socket.on("dungeon:combat_step", async ({ enemyId }) => {
|
||||
try {
|
||||
const session = sessionManager.getPlayerSession(socket.id);
|
||||
if (!session || session.scene !== "dungeon" || !session.sceneData) return;
|
||||
if (!userId) return;
|
||||
|
||||
const session = dungeonManager.activeSessions.get(userId);
|
||||
if (!session || session.isFinished) return;
|
||||
|
||||
const dungeonSession = session.sceneData;
|
||||
const enemyId = sessionManager._cleanId(data.enemyId);
|
||||
const enemyTemplate = DatapackLoader.getEnemy(enemyId);
|
||||
|
||||
if (!enemyTemplate) {
|
||||
return socket.emit("error", { message: "Target data corrupted" });
|
||||
}
|
||||
|
||||
if (dungeonSession.currentEnemyHp === undefined) {
|
||||
dungeonSession.currentEnemyHp = enemyTemplate.stats?.hp || 100;
|
||||
if (session.currentEnemyHp === undefined) {
|
||||
session.currentEnemyHp = enemyTemplate.stats?.health || 100;
|
||||
}
|
||||
|
||||
const damage = Math.floor(Math.random() * 11) + 15;
|
||||
dungeonSession.currentEnemyHp -= damage;
|
||||
const damage = Math.floor(Math.random() * 10) + 20;
|
||||
session.currentEnemyHp -= damage;
|
||||
|
||||
const isDefeated = dungeonSession.currentEnemyHp <= 0;
|
||||
const isDefeated = session.currentEnemyHp <= 0;
|
||||
|
||||
socket.emit("dungeon:combat_result", {
|
||||
damageDealt: damage,
|
||||
enemyHp: Math.max(0, dungeonSession.currentEnemyHp),
|
||||
enemyHp: Math.max(0, session.currentEnemyHp),
|
||||
targetDefeated: isDefeated,
|
||||
message: `Strike successful. Dealt ${damage} damage to units.`,
|
||||
message: `Strike successful. Dealt ${damage} damage.`,
|
||||
});
|
||||
|
||||
if (isDefeated) {
|
||||
console.log(`[Dungeon] Enemy ${enemyId} defeated by ${session.id}`);
|
||||
session.currentEnemyHp = undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Combat Error:", err);
|
||||
@ -90,62 +81,49 @@ module.exports = (io, socket) => {
|
||||
|
||||
socket.on("dungeon:next_room", async () => {
|
||||
try {
|
||||
const session = sessionManager.getPlayerSession(socket.id);
|
||||
if (!userId) return;
|
||||
|
||||
if (!session || session.scene !== "dungeon" || !session.sceneData) {
|
||||
console.error(
|
||||
`[Dungeon Error] Invalid session for socket ${socket.id}`,
|
||||
);
|
||||
return;
|
||||
const nextRoom = dungeonManager.moveToNextRoom(userId);
|
||||
if (!nextRoom) {
|
||||
return socket.emit("error", {
|
||||
message: "Could not proceed to next room",
|
||||
});
|
||||
}
|
||||
|
||||
const dungeonId = session.sceneData.dungeonId; // ТУТ була помилка (читання з undefined)
|
||||
const dungeon = DatapackLoader.getDungeon(dungeonId);
|
||||
|
||||
const nextIndex = session.sceneData.currentRoomIndex + 1;
|
||||
|
||||
if (nextIndex < dungeon.rooms.length) {
|
||||
session.sceneData.currentRoomIndex = nextIndex;
|
||||
|
||||
const nextRoomId = dungeon.rooms[nextIndex].id;
|
||||
const nextRoomData = DatapackLoader.getRoom(nextRoomId);
|
||||
|
||||
socket.emit("dungeon:room_update", {
|
||||
room: nextRoomData,
|
||||
roomIndex: nextIndex,
|
||||
});
|
||||
if (nextRoom.status === "completed") {
|
||||
await finalizeDungeon(socket, nextRoom.rewards);
|
||||
} else {
|
||||
await finalizeDungeon(socket, session.sceneData, dungeon);
|
||||
socket.emit("dungeon:room_update", {
|
||||
room: nextRoom.config,
|
||||
hostiles: nextRoom.hostiles,
|
||||
roomIndex: nextRoom.roomIndex,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Dungeon Progress Error:", err);
|
||||
socket.emit("error", { message: "Failed to process next room" });
|
||||
socket.emit("error", { message: "Navigation system error" });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("dungeon:leave", () => {
|
||||
if (userId) dungeonManager.leaveDungeon(userId);
|
||||
});
|
||||
};
|
||||
|
||||
async function finalizeDungeon(socket, sessionData, dungeon) {
|
||||
async function finalizeDungeon(socket, sessionRewards) {
|
||||
const userId = socket.user.id;
|
||||
const earnedLoot = [];
|
||||
try {
|
||||
const player = await Player.findByPk(userId);
|
||||
|
||||
dungeon.lootTable.forEach((entry) => {
|
||||
if (Math.random() * 100 <= entry.chance) {
|
||||
earnedLoot.push({ itemId: entry.itemId, quantity: 1 });
|
||||
if (sessionRewards.credits > 0) {
|
||||
await player.increment("credits", { by: sessionRewards.credits });
|
||||
}
|
||||
});
|
||||
|
||||
for (const item of earnedLoot) {
|
||||
const [invItem, created] = await Inventory.findOrCreate({
|
||||
where: { playerId: userId, itemId: item.itemId },
|
||||
defaults: { quantity: 0 },
|
||||
socket.emit("dungeon:completed", {
|
||||
rewards: sessionRewards,
|
||||
message: "Mission successful. All objectives secured.",
|
||||
});
|
||||
await invItem.increment("quantity", { by: item.quantity });
|
||||
} finally {
|
||||
dungeonManager.leaveDungeon(userId);
|
||||
}
|
||||
|
||||
sessionManager.setPlayerScene(socket.id, "world", null);
|
||||
|
||||
socket.emit("dungeon:completed", {
|
||||
rewards: earnedLoot,
|
||||
message: "Mission successful. All objectives secured.",
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user