diff --git a/client/src/services/GameDataManager.js b/client/src/services/GameDataManager.js
index a6fdcc7..76ebee5 100644
--- a/client/src/services/GameDataManager.js
+++ b/client/src/services/GameDataManager.js
@@ -36,17 +36,31 @@ class GameDataManager {
data.quests.forEach((q) => this.quests.set(q.id, q));
}
- console.log(this.quests);
+ console.log(this.skills);
if (data.languages) {
this.translations = data.languages;
}
if (data.manifest) {
this.manifest = data.manifest;
}
+ console.log(this.manifest);
this.isLoaded = true;
}
+ getSkillCategories() {
+ return this._getCategoriesFromManifest("skills");
+ }
+ getSkillsByCategory(category) {
+ console.log(this.skills, category, "CATEGORY");
+ return Array.from(this.skills.values())
+ .filter((skill) => skill.meta.category === category)
+ .map((skill) => ({
+ ...skill,
+ displayName: this.t(skill.displayName),
+ description: this.t(skill.description),
+ }));
+ }
t(key) {
if (!key) return "";
const langData = this.translations[this.currentLang];
diff --git a/client/src/views/GameInterface/components/DungeonScreen.css b/client/src/views/GameInterface/components/DungeonScreen.css
index e605352..c8d07a0 100644
--- a/client/src/views/GameInterface/components/DungeonScreen.css
+++ b/client/src/views/GameInterface/components/DungeonScreen.css
@@ -328,3 +328,22 @@
margin-top: 4px;
opacity: 0.8;
}
+
+.mob-stats-display {
+ display: flex;
+ gap: 8px;
+ font-size: 0.65rem;
+ margin-top: 5px;
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.player-stats-row {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 10px;
+ font-size: 0.8rem;
+ color: #00d2ff;
+ font-family: "Orbitron", sans-serif;
+ border-top: 1px solid rgba(0, 210, 255, 0.2);
+ padding-top: 5px;
+}
diff --git a/client/src/views/GameInterface/components/DungeonScreen.jsx b/client/src/views/GameInterface/components/DungeonScreen.jsx
index 3481c63..4656567 100644
--- a/client/src/views/GameInterface/components/DungeonScreen.jsx
+++ b/client/src/views/GameInterface/components/DungeonScreen.jsx
@@ -4,105 +4,19 @@ import "./DungeonScreen.css";
import DungeonFinish from "../tabs/components/DungeonFinish.jsx";
const DungeonScreen = ({ session, socket }) => {
- const [roomData, setRoomData] = useState(session.room);
- const [roomIndex, setRoomIndex] = useState(session.roomIndex);
const [battle, setBattle] = useState(session.battle || null);
+ const [roomIndex, setRoomIndex] = useState(session.roomIndex);
+ const [totalRooms, setTotalRooms] = useState(session.totalRooms || 1);
const [timeLeft, setTimeLeft] = useState(10);
const [summary, setSummary] = useState(null);
const [activeAttacker, setActiveAttacker] = useState(null);
-
const [selectedTarget, setSelectedTarget] = useState(null);
-
const [log, setLog] = useState([
"SYSTEM: Neural link established. Scanning sector...",
]);
const logEndRef = useRef(null);
const timerRef = useRef(null);
- const dungeonData = GameDataManager.getDungeon(session.dungeonId);
-
- useEffect(() => {
- logEndRef.current?.scrollIntoView({ behavior: "smooth" });
- }, [log]);
-
- useEffect(() => {
- setSelectedTarget(null);
- }, [battle?.currentTurnIndex]);
-
- useEffect(() => {
- if (!battle || battle.isOver || activeAttacker) return;
-
- const isPlayer = battle.turnOrder[battle.currentTurnIndex] === "player";
- const maxTime = isPlayer ? 10 : 4;
- setTimeLeft(maxTime);
-
- if (timerRef.current) clearInterval(timerRef.current);
-
- timerRef.current = setInterval(() => {
- setTimeLeft((prev) => {
- if (prev <= 1) {
- if (isPlayer) handleCombatAction();
- return 0;
- }
- return prev - 1;
- });
- }, 1000);
-
- return () => clearInterval(timerRef.current);
- }, [battle?.currentTurnIndex, battle?.isOver, activeAttacker]);
-
- useEffect(() => {
- socket.on("dungeon:room_update", (data) => {
- setRoomData(data.room);
- setRoomIndex(data.roomIndex);
- setBattle(data.battle);
- addLog(`--- ENTERING SECTOR ${data.roomIndex + 1} ---`);
- });
-
- socket.on("dungeon:failed", (data) => {
- addLog(`--- TERMINAL ERROR: ${data.message} ---`);
- setTimeout(() => window.location.reload(), 3000);
- });
-
- socket.on("dungeon:battle_update", async (data) => {
- const turnOrder = data.battle.turnOrder;
- const lastIndex =
- (data.battle.currentTurnIndex - 1 + turnOrder.length) %
- turnOrder.length;
- const lastActorId = turnOrder[lastIndex];
-
- if (lastActorId !== "player" && !data.battle.isOver) {
- setActiveAttacker(lastActorId);
- await new Promise((resolve) => setTimeout(resolve, 2000));
- setBattle(data.battle);
- if (data.log) data.log.forEach((msg) => addLog(msg));
- setActiveAttacker(null);
- } else {
- setBattle(data.battle);
- if (data.log) data.log.forEach((msg) => addLog(msg));
- setActiveAttacker(null);
- }
-
- if (data.status === "victory")
- addLog("MISSION_OBJECTIVE: Threats neutralized.");
- if (data.status === "defeat") {
- addLog("CRITICAL_ERROR: Bio-sign lost.");
- setTimeout(() => window.location.reload(), 3000);
- }
- });
-
- socket.on("dungeon:completed", (data) => {
- setSummary(data.rewards);
- addLog("MISSION_SUCCESS: All objectives secured.");
- });
-
- return () => {
- socket.off("dungeon:room_update");
- socket.off("dungeon:battle_update");
- socket.off("dungeon:completed");
- socket.off("dungeon:failed");
- };
- }, [socket]);
const addLog = (text) => {
const time = new Date().toLocaleTimeString([], {
@@ -114,21 +28,111 @@ const DungeonScreen = ({ session, socket }) => {
setLog((prev) => [...prev, `[${time}] ${text}`]);
};
- const handleCombatAction = () => {
- const targetId = selectedTarget;
- if (!battle || battle.isOver || activeAttacker || !targetId) return;
- if (battle.turnOrder[battle.currentTurnIndex] !== "player") return;
+ useEffect(() => {
+ logEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [log]);
+
+ useEffect(() => {
+ if (!battle || battle.isOver || activeAttacker) {
+ if (timerRef.current) clearInterval(timerRef.current);
+ return;
+ }
+
+ const isPlayer = battle.turnOrder[battle.currentTurnIndex] === "player";
+ if (!isPlayer) return;
+
+ setTimeLeft(10);
+ if (timerRef.current) clearInterval(timerRef.current);
+
+ timerRef.current = setInterval(() => {
+ setTimeLeft((prev) => {
+ if (prev <= 1) {
+ handleCombatAction(null);
+ return 0;
+ }
+ return prev - 1;
+ });
+ }, 1000);
+
+ return () => clearInterval(timerRef.current);
+ }, [battle?.currentTurnIndex, battle?.isOver, activeAttacker]);
+
+ useEffect(() => {
+ socket.on("dungeon:battle_update", async (data) => {
+ if (data.log && Array.isArray(data.log)) {
+ for (const action of data.log) {
+ if (typeof action === "object" && action.attackerId) {
+ setActiveAttacker(action.attackerId);
+ action.messages?.forEach((msg) => addLog(msg));
+
+ if (action.hpState) {
+ setBattle((prev) => ({
+ ...prev,
+ player: { ...prev.player, hp: action.hpState.playerHp },
+ enemies: prev.enemies.map((e) => {
+ const s = action.hpState.enemies.find(
+ (ae) => ae.id === e.instanceId,
+ );
+ return s ? { ...e, hp: s.hp, isDead: s.isDead } : e;
+ }),
+ }));
+ }
+ await new Promise((r) => setTimeout(r, 1500));
+ } else if (typeof action === "string") {
+ addLog(action);
+ }
+ }
+ }
+
+ setBattle(data.battle);
+ setActiveAttacker(null);
+ setSelectedTarget(null);
+
+ if (data.status === "victory")
+ addLog("MISSION_OBJECTIVE: Threats neutralized.");
+ if (data.status === "defeat") {
+ addLog("CRITICAL_ERROR: Bio-sign lost.");
+ setTimeout(() => window.location.reload(), 3000);
+ }
+ });
+
+ socket.on("dungeon:room_update", (data) => {
+ setRoomIndex(data.roomIndex);
+ setTotalRooms(data.totalRooms);
+ setBattle(data.battle);
+ addLog(`--- ENTERING SECTOR ${data.roomIndex + 1} ---`);
+ });
+
+ socket.on("dungeon:completed", (data) => {
+ setSummary(data.rewards);
+ addLog("MISSION_SUCCESS: All objectives secured.");
+ });
+
+ return () => {
+ socket.off("dungeon:battle_update");
+ socket.off("dungeon:room_update");
+ socket.off("dungeon:completed");
+ };
+ }, [socket]);
+
+ const handleCombatAction = (targetId = selectedTarget) => {
+ const isPlayer = battle?.turnOrder[battle?.currentTurnIndex] === "player";
+ if (!battle || battle.isOver || activeAttacker || !isPlayer) return;
socket.emit("dungeon:combat_action", { targetInstanceId: targetId });
- addLog(`Initiating strike sequence...`);
- setSelectedTarget(null); // Скидаємо вибір після атаки
+ if (!targetId) addLog("Sequence timeout! Skipping...");
+ else addLog("Initiating strike sequence...");
+ setSelectedTarget(null);
};
const handleNextRoom = () => {
socket.emit("dungeon:next_room");
};
- const isPlayerTurn = battle?.turnOrder[battle?.currentTurnIndex] === "player";
+ const isPlayerTurn =
+ battle?.turnOrder[battle?.currentTurnIndex] === "player" &&
+ !activeAttacker &&
+ !battle.isOver;
return (
@@ -139,40 +143,35 @@ const DungeonScreen = ({ session, socket }) => {
/>
)}
- {/* Header section remains the same */}
- SECTOR {roomIndex + 1} / {session.totalRooms}
+ SECTOR {roomIndex + 1} / {totalRooms}
- {battle && (
+ {battle && !battle.isOver && (
- {isPlayerTurn ? "YOUR TURN" : "ENEMY ACTION"}
+ {isPlayerTurn ? "YOUR TURN" : "PROCESSING..."}
)}
@@ -184,29 +183,18 @@ const DungeonScreen = ({ session, socket }) => {
{battle.enemies.map((mob) => (
- !mob.isDead &&
isPlayerTurn &&
+ !mob.isDead &&
setSelectedTarget(mob.instanceId)
}
>
- {selectedTarget === mob.instanceId && (
-
-
-
- )}
-
{
>
{GameDataManager.t(mob.name)}
- {!mob.isDead &&
ATK: {mob.atk}}
))}
@@ -229,7 +216,7 @@ const DungeonScreen = ({ session, socket }) => {
{battle && (
COMMANDER_INTEGRITY
@@ -243,40 +230,33 @@ const DungeonScreen = ({ session, socket }) => {
style={{
width: `${(battle.player.hp / battle.player.maxHp) * 100}%`,
}}
- >
+ />
)}
-
-
-
- {log.map((entry, i) => (
-
- > {entry}
-
- ))}
-
-
+
+ {log.map((entry, i) => (
+
+ > {entry}
+
+ ))}
+
-
{battle && !battle.isOver && (
)}
- {((battle?.isOver && battle.player.hp > 0) || !battle) && (
+ {((battle?.isOver && battle.player.hp > 0) || !battle) && !summary && (
diff --git a/client/src/views/GameInterface/tabs/SkillsTab.jsx b/client/src/views/GameInterface/tabs/SkillsTab.jsx
index 94958ac..3a197a5 100644
--- a/client/src/views/GameInterface/tabs/SkillsTab.jsx
+++ b/client/src/views/GameInterface/tabs/SkillsTab.jsx
@@ -1,160 +1,115 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState, useEffect } from "react";
+import { useSocket } from "../../../hooks/useSocket";
+import GameDataManager from "../../../services/GameDataManager";
import "./styles/SkillsTab.css";
+import CategorySelector from "../components/CategorySelector";
+import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx";
+import { SkillCard } from "./components/SkillsCard.jsx";
const SkillsTab = () => {
- const [category, setCategory] = useState('combat');
- const categories = [
- { id: 'combat', label: 'Combat' },
- { id: 'science', label: 'Science' },
- { id: 'crafting', label: 'Crafting' }
- ];
+ const { socket } = useSocket();
- const [skillsData] = useState(() => {
- const catIds = categories.map(c => c.id);
- return Array.from({ length: 5 }, (_, i) => ({
- id: `skill-${i}`,
- category: catIds[i % catIds.length],
- name: `Tech ${i + 1}`,
- currentLevel: i < 3 ? 1 : 0,
- maxLevel: 10,
- experience: 20,
- experienceToNext: 100,
- description: "Specialized module for advanced space operations.",
- iconClass: i % 3 === 0 ? "fa-shield-alt" : i % 3 === 1 ? "fa-flask" : "fa-tools",
- unlocked: i < 3,
- requiredLevel: 1
- }));
- });
- const handleUnlock = (category, id) => {
- console.log(`Unlocking ${id} in ${category}`);
- };
+ const [categories, setCategories] = useState([]);
+ const [activeCategory, setActiveCategory] = useState("");
+ const [skills, setSkills] = useState([]);
+ const [playerSkills, setPlayerSkills] = useState({});
+ const [skillPoints, setSkillPoints] = useState(0);
- const handleUpgrade = (category, id) => {
- console.log(`Upgrading ${id} in ${category}`);
+ useEffect(() => {
+ const manifestCategories = GameDataManager.getSkillCategories();
+ setCategories(manifestCategories);
+
+ if (manifestCategories.length > 0) {
+ setActiveCategory(manifestCategories[0].id);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (activeCategory) {
+ const filtered = GameDataManager.getSkillsByCategory(activeCategory);
+ setSkills(filtered);
+ }
+ }, [activeCategory]);
+
+ useEffect(() => {
+ if (!socket) return;
+
+ socket.emit("player:get_skill_points");
+ socket.emit("player:get_skills");
+
+ const handleSkillPoints = (data) => setSkillPoints(data.points || 0);
+ const handleSkillsData = (data) => setPlayerSkills(data.skills || {});
+
+ socket.on("player:skill_points_data", handleSkillPoints);
+ socket.on("player:skills_data", handleSkillsData);
+
+ return () => {
+ socket.off("player:skill_points_data", handleSkillPoints);
+ socket.off("player:skills_data", handleSkillsData);
+ };
+ }, [socket]);
+
+ const handleUpgrade = (skillId) => {
+ socket.emit("player:upgrade_skill", { skillId });
};
- const filteredSkills = skillsData.filter(skill => skill.category === category);
return (
-
-
+
+
-
Skills
-
-
Skill Points: 0
+
+
+ Neural Core
+
+
+ Uplink Points:
+ {skillPoints}
+
-
- {categories.map(cat => (
-
- ))}
-
-
- {filteredSkills.length > 0 ? (
- filteredSkills.map(skill => (
-
console.log('Unlock', cat, id)}
- onUpgrade={(cat, id) => console.log('Upgrade', cat, id)}
- />
- ))
+
+
+
+
+ {skills.length > 0 ? (
+ skills.map((skill) => {
+ const progress = playerSkills[skill.id] || {
+ level: 0,
+ experience: 0,
+ };
+ const maxLv = skill.meta?.topLevel || 10;
+
+ const cost = progress.level === 0 ? 2 : 1;
+
+ return (
+
= cost}
+ onUpgrade={() => handleUpgrade(skill.id)}
+ />
+ );
+ })
) : (
- No skills discovered in this category yet.
+
+
+
No active modules found in this sector.
+
)}
-
-
- );
-};
-
-const SkillComponent = ({
- skill,
- activeCategory,
- skillId,
- onUnlock,
- onUpgrade,
-}) => {
- const {
- name,
- currentLevel,
- maxLevel,
- description,
- iconClass,
- experience,
- experienceToNext,
- unlocked,
- requiredLevel
- } = skill;
-
- const progressPercent = (experience / experienceToNext) * 100;
-
- return (
-
-
-
-
-
-
-
{name}
-
Lv. {currentLevel}/{maxLevel}
-
-
-
-
{description}
-
- {unlocked && currentLevel < maxLevel && (
-
-
-
{experience}/{experienceToNext} XP
-
- )}
-
- {currentLevel >= maxLevel && (
-
- MAX LEVEL REACHED
-
- )}
-
-
- {!unlocked ? (
-
- ) : currentLevel < maxLevel ? (
-
- ) : (
- Mastered
- )}
-
-
- {!skill.unlocked ?
-
Requires Level {skill.requiredLevel}
- : ''}
-
+
);
};
export default SkillsTab;
-
diff --git a/client/src/views/GameInterface/tabs/components/SkillsCard.css b/client/src/views/GameInterface/tabs/components/SkillsCard.css
new file mode 100644
index 0000000..7ef375c
--- /dev/null
+++ b/client/src/views/GameInterface/tabs/components/SkillsCard.css
@@ -0,0 +1,175 @@
+.skill-item-card {
+ background: rgba(10, 15, 24, 0.95);
+ border: 1px solid #1a2638;
+ border-radius: 2px;
+ padding: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ position: relative;
+ transition: all 0.3s ease;
+ overflow: hidden;
+}
+
+.skill-item-card:hover {
+ border-color: #00d4ff;
+ box-shadow: 0 0 15px rgba(0, 212, 255, 0.1);
+}
+
+.skill-item-card.locked {
+ border-style: dashed;
+ opacity: 0.8;
+}
+
+.skill-item-card.locked .skill-icon-wrapper {
+ color: #4a5d75;
+ border-color: #1a2638;
+}
+
+.skill-item-card.mastered {
+ border-color: #00ff88;
+}
+
+.skill-card-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.skill-icon-wrapper {
+ width: 40px;
+ height: 40px;
+ background: #05080c;
+ border: 1px solid #00d4ff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ color: #00d4ff;
+ flex-shrink: 0;
+}
+
+.skill-title-block {
+ flex: 1;
+}
+
+.skill-name {
+ font-size: 0.9rem;
+ font-weight: 900;
+ color: #fff;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 2px;
+}
+
+.skill-level-tag {
+ font-size: 10px;
+ color: #4a5d75;
+ font-family: "Space Mono", monospace;
+}
+
+.mastered .skill-level-tag {
+ color: #00ff88;
+}
+
+.skill-desc {
+ font-size: 11px;
+ color: #a0aec0;
+ line-height: 1.4;
+ margin: 0;
+ min-height: 32px;
+}
+
+.skill-progress-section {
+ margin-top: 5px;
+}
+
+.progress-info {
+ display: flex;
+ justify-content: space-between;
+ font-size: 9px;
+ color: #4a5d75;
+ margin-bottom: 4px;
+ text-transform: uppercase;
+}
+
+.skill-progress-bar {
+ height: 4px;
+ background: #05080c;
+ border: 1px solid #1a2638;
+ position: relative;
+}
+
+.skill-progress-bar .fill {
+ height: 100%;
+ background: linear-gradient(90deg, #00d4ff, #00ff88);
+ transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.skill-card-actions {
+ margin-top: auto;
+ padding-top: 10px;
+}
+
+.btn-skill-unlock,
+.btn-skill-upgrade {
+ width: 100%;
+ background: transparent;
+ border: 1px solid #00d4ff;
+ color: #00d4ff;
+ padding: 8px;
+ font-size: 10px;
+ font-weight: 900;
+ text-transform: uppercase;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-family: "Space Mono", monospace;
+}
+
+.btn-skill-unlock:hover:not(:disabled),
+.btn-skill-upgrade:hover:not(:disabled) {
+ background: rgba(0, 212, 255, 0.1);
+ box-shadow: 0 0 10px rgba(0, 212, 255, 0.2);
+}
+
+.btn-skill-unlock {
+ border-color: #ffaa00;
+ color: #ffaa00;
+}
+
+.btn-skill-unlock:hover:not(:disabled) {
+ background: rgba(255, 170, 0, 0.1);
+ box-shadow: 0 0 10px rgba(255, 170, 0, 0.2);
+}
+
+.btn-skill-unlock:disabled,
+.btn-skill-upgrade:disabled {
+ border-color: #1a2638;
+ color: #4a5d75;
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+.mastery-label {
+ text-align: center;
+ font-size: 10px;
+ color: #00ff88;
+ font-weight: 900;
+ padding: 8px;
+ border: 1px solid rgba(0, 255, 136, 0.2);
+ background: rgba(0, 255, 136, 0.05);
+}
+
+.lock-requirement {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ background: rgba(255, 68, 68, 0.1);
+ color: #ff4444;
+ font-size: 8px;
+ text-align: center;
+ padding: 2px 0;
+ text-transform: uppercase;
+ font-weight: 900;
+}
diff --git a/client/src/views/GameInterface/tabs/components/SkillsCard.jsx b/client/src/views/GameInterface/tabs/components/SkillsCard.jsx
new file mode 100644
index 0000000..54fdbf1
--- /dev/null
+++ b/client/src/views/GameInterface/tabs/components/SkillsCard.jsx
@@ -0,0 +1,76 @@
+import "./SkillsCard.css";
+
+export const SkillCard = ({
+ skill,
+ level,
+ maxLevel,
+ experience,
+ onUpgrade,
+ canAfford,
+}) => {
+ const isLocked = level === 0;
+ const isMaxLevel = level >= maxLevel;
+
+ const expToNext = Math.floor(100 * Math.pow(1.5, level));
+ const progressPercent = Math.min(100, (experience / expToNext) * 100);
+
+ return (
+
+
+
+ {/* Іконка може бути в meta або за дефолтом */}
+
+
+
+
{skill.displayName}
+
+ {isMaxLevel ? "MAXED" : `RANK ${level}/${maxLevel}`}
+
+
+
+
+
{skill.description}
+
+ {!isMaxLevel && !isLocked && (
+
+
+ Neural Sync
+
+ {Math.floor(experience)} / {expToNext}
+
+
+
+
+ )}
+
+
+ {isLocked ? (
+
+ ) : isMaxLevel ? (
+
MODULE FULLY OPTIMIZED
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/client/src/views/GameInterface/tabs/styles/SkillsTab.css b/client/src/views/GameInterface/tabs/styles/SkillsTab.css
index a6c22a2..4f199b2 100644
--- a/client/src/views/GameInterface/tabs/styles/SkillsTab.css
+++ b/client/src/views/GameInterface/tabs/styles/SkillsTab.css
@@ -1,42 +1,133 @@
.skills-container {
- max-width: 1200px;
- margin: 0 auto;
- max-height: calc(100vh - 232px); /* Adjusted for title bar and padding */
- overflow-y: auto;
- padding: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: 20px;
+ box-sizing: border-box;
+ position: relative;
+ overflow: hidden;
}
-.skill-categories {
- display: flex;
- gap: 0.5rem;
- margin-bottom: 2rem;
- justify-content: center;
+.skills-header {
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid rgba(0, 212, 255, 0.1);
}
-.skill-cat-btn {
- padding: 0.75rem 1.5rem;
- background: var(--bg-tertiary);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- color: var(--text-secondary);
- cursor: pointer;
- transition: all 0.3s ease;
+.header-main {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
-.skill-cat-btn:hover {
- border-color: var(--primary-color);
- color: var(--text-primary);
+.header-main h2 {
+ margin: 0;
+ font-size: 1.2rem;
+ color: #fff;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
}
-.skill-cat-btn.active {
- background: var(--gradient-primary);
- color: var(--bg-primary);
- border-color: transparent;
+.header-main h2 i {
+ color: #00d4ff;
+ text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
+}
+
+.skill-points-badge {
+ background: rgba(0, 212, 255, 0.1);
+ border: 1px solid #00d4ff;
+ padding: 5px 15px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.skill-points-badge .label {
+ font-size: 9px;
+ color: #4a5d75;
+ text-transform: uppercase;
+ font-weight: 900;
+}
+
+.skill-points-badge .value {
+ font-size: 1.1rem;
+ color: #fff;
+ font-family: "Space Mono", monospace;
+ font-weight: 900;
}
.skills-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 1rem;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 15px;
+ overflow-y: auto;
+ padding-right: 10px;
+ flex: 1;
}
+.custom-scroll::-webkit-scrollbar {
+ width: 4px;
+}
+
+.custom-scroll::-webkit-scrollbar-track {
+ background: rgba(5, 8, 12, 0.5);
+}
+
+.custom-scroll::-webkit-scrollbar-thumb {
+ background: #1a2638;
+ border-radius: 2px;
+}
+
+.custom-scroll::-webkit-scrollbar-thumb:hover {
+ background: #00d4ff;
+}
+
+.empty-category {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px;
+ color: #4a5d75;
+ text-align: center;
+ border: 1px dashed #1a2638;
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.empty-category i {
+ font-size: 2rem;
+ margin-bottom: 15px;
+ opacity: 0.5;
+}
+
+.empty-category p {
+ font-family: "Space Mono", monospace;
+ font-size: 12px;
+ text-transform: uppercase;
+}
+
+@media (max-width: 600px) {
+ .skills-container {
+ padding: 10px;
+ }
+
+ .header-main {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .skill-points-badge {
+ width: 100%;
+ justify-content: space-between;
+ box-sizing: border-box;
+ }
+
+ .skills-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/game-server/datapacks/original/data/dungeons/themed/kaleidoscope.json b/game-server/datapacks/original/data/dungeons/themed/kaleidoscope.json
index cf3abb3..5be6251 100644
--- a/game-server/datapacks/original/data/dungeons/themed/kaleidoscope.json
+++ b/game-server/datapacks/original/data/dungeons/themed/kaleidoscope.json
@@ -4,7 +4,7 @@
"displayName": "dungeons.original.Kaleidoscope",
"description": "dungeons.original.Kaleidoscope.desc",
"meta": {
- "energyCost": 10,
+ "energyCost": 0,
"repeatable": true,
"raid": false
},
@@ -23,4 +23,5 @@
}
]
}
-}
\ No newline at end of file
+}
+
diff --git a/game-server/datapacks/original/data/enemies/rooms/themed/broken_reactor.json b/game-server/datapacks/original/data/enemies/rooms/themed/broken_reactor.json
index 69cf677..bf576d2 100644
--- a/game-server/datapacks/original/data/enemies/rooms/themed/broken_reactor.json
+++ b/game-server/datapacks/original/data/enemies/rooms/themed/broken_reactor.json
@@ -1,5 +1,5 @@
{
- "room": {
+ "rooms": {
"id": "original:themed/broken_reactor",
"displayName": "rooms.original.themed.broken_reactor",
"description": "rooms.original.themed.broken_reactor.desc",
@@ -41,4 +41,5 @@
"isBossRoom": true
}
}
-}
\ No newline at end of file
+}
+
diff --git a/game-server/datapacks/original/data/enemies/rooms/themed/cold.json b/game-server/datapacks/original/data/enemies/rooms/themed/cold.json
index 5106a7e..abd14aa 100644
--- a/game-server/datapacks/original/data/enemies/rooms/themed/cold.json
+++ b/game-server/datapacks/original/data/enemies/rooms/themed/cold.json
@@ -1,5 +1,5 @@
{
- "room": {
+ "rooms": {
"id": "original:themed/cold",
"displayName": "rooms.original.themed.cold",
"description": "rooms.original.themed.cold.desc",
@@ -14,4 +14,5 @@
"isBossRoom": false
}
}
-}
\ No newline at end of file
+}
+
diff --git a/game-server/datapacks/original/data/enemies/rooms/themed/heat_anomaly.json b/game-server/datapacks/original/data/enemies/rooms/themed/heat_anomaly.json
index 1392fa7..6178172 100644
--- a/game-server/datapacks/original/data/enemies/rooms/themed/heat_anomaly.json
+++ b/game-server/datapacks/original/data/enemies/rooms/themed/heat_anomaly.json
@@ -1,5 +1,5 @@
{
- "room": {
+ "rooms": {
"id": "original:themed/heat_anomaly",
"displayName": "rooms.original.themed.heat_anomaly",
"description": "rooms.original.themed.heat_anomaly.desc",
@@ -15,4 +15,5 @@
"isBossRoom": false
}
}
-}
\ No newline at end of file
+}
+
diff --git a/game-server/datapacks/original/data/enemies/rooms/themed/the_rat_one.json b/game-server/datapacks/original/data/enemies/rooms/themed/the_rat_one.json
index 7f759f2..aa5ae94 100644
--- a/game-server/datapacks/original/data/enemies/rooms/themed/the_rat_one.json
+++ b/game-server/datapacks/original/data/enemies/rooms/themed/the_rat_one.json
@@ -1,5 +1,5 @@
{
- "room": {
+ "rooms": {
"id": "original:themed/the_rat_one",
"displayName": "rooms.original.themed.the_rat_one",
"description": "rooms.original.themed.the_rat_one.desc",
@@ -24,4 +24,5 @@
"isBossRoom": false
}
}
-}
\ No newline at end of file
+}
+
diff --git a/game-server/datapacks/original/data/skills/combat/engine_effiency.json b/game-server/datapacks/original/data/skills/combat/engine_effiency.json
index 228c288..d036020 100644
--- a/game-server/datapacks/original/data/skills/combat/engine_effiency.json
+++ b/game-server/datapacks/original/data/skills/combat/engine_effiency.json
@@ -4,7 +4,7 @@
"displayName": "skills.category.original.combat.engine_effiency",
"description": "skills.category.original.combat.engine_effiency.desc",
"meta": {
- "category": "combat",
+ "category": "original:combat",
"topLevel": 10,
"math": {
"start": 500,
diff --git a/game-server/datapacks/original/data/skills/combat/shield_effiency.json b/game-server/datapacks/original/data/skills/combat/shield_effiency.json
index dae5e05..1627747 100644
--- a/game-server/datapacks/original/data/skills/combat/shield_effiency.json
+++ b/game-server/datapacks/original/data/skills/combat/shield_effiency.json
@@ -4,7 +4,7 @@
"displayName": "skills.category.original.combat.shield_effiency",
"description": "skills.category.original.combat.shield_effiency.desc",
"meta": {
- "category": "combat",
+ "category": "original:combat",
"topLevel": 10,
"math": {
"start": 500,
diff --git a/game-server/datapacks/original/data/skills/combat/thruster_speed.json b/game-server/datapacks/original/data/skills/combat/thruster_speed.json
index 99a780f..9ae9427 100644
--- a/game-server/datapacks/original/data/skills/combat/thruster_speed.json
+++ b/game-server/datapacks/original/data/skills/combat/thruster_speed.json
@@ -4,7 +4,7 @@
"displayName": "skills.category.original.combat.thruster_effiency",
"description": "skills.category.original.combat.thruster_effiency.desc",
"meta": {
- "category": "combat",
+ "category": "original:combat",
"topLevel": 10,
"math": {
"start": 500,
diff --git a/game-server/datapacks/original/data/skills/combat/weapon_effiency.json b/game-server/datapacks/original/data/skills/combat/weapon_effiency.json
index 7119d21..e9c985f 100644
--- a/game-server/datapacks/original/data/skills/combat/weapon_effiency.json
+++ b/game-server/datapacks/original/data/skills/combat/weapon_effiency.json
@@ -4,7 +4,7 @@
"displayName": "skills.category.original.combat.weapon_effiency",
"description": "skills.category.original.combat.weapon_effiency.desc",
"meta": {
- "category": "combat",
+ "category": "original:combat",
"topLevel": 10,
"math": {
"start": 500,
diff --git a/game-server/src/game/CombatService.js b/game-server/src/game/CombatService.js
index 3ba99d9..5b37840 100644
--- a/game-server/src/game/CombatService.js
+++ b/game-server/src/game/CombatService.js
@@ -4,78 +4,69 @@ class CombatService {
initializeBattle(player, hostiles) {
const equipmentStats = this.calculateEquipmentStats(player.equipment);
- const maxHp = 100 + (equipmentStats.health || 0);
- const atk = 25 + (equipmentStats.attack || 0);
+ const playerMaxHp = 100 + (equipmentStats.health || 0);
+ const playerAtk = 25 + (equipmentStats.attack || 0);
+ const playerDef = equipmentStats.defence || 0;
+ const playerRes = equipmentStats.resistance || 0;
const battle = {
player: {
id: player.id,
name: player.username || "Commander",
- hp: maxHp,
- maxHp: maxHp,
- atk: atk,
+ hp: playerMaxHp,
+ maxHp: playerMaxHp,
+ atk: playerAtk,
+ def: playerDef,
+ res: playerRes,
stats: equipmentStats,
},
- enemies: hostiles.map((h, index) => ({
- ...h,
- instanceId: `mob_${index}`,
- id: h.id,
- name: h.displayName || h.name || `Hostile ${index + 1}`,
- hp: h.stats?.health || 50,
- maxHp: h.stats?.health || 50,
- atk: h.stats?.attack || 10,
- isDead: false,
- })),
- turnOrder: [],
+ enemies: hostiles.map((h, index) => {
+ const hHp = h.stats?.health || 50;
+ return {
+ ...h,
+ instanceId: `mob_${index}`,
+ id: h.id,
+ name: h.displayName || h.name || `Hostile ${index + 1}`,
+ hp: hHp,
+ maxHp: hHp,
+ atk: h.stats?.attack || 10,
+ def: h.stats?.defence || 0,
+ res: h.stats?.resistance || 0,
+ isDead: false,
+ rewardGiven: false,
+ gainXp: h.gainXp || 0,
+ credits: h.credits || 0,
+ loot: h.loot || [],
+ };
+ }),
+ turnOrder: ["player", ...hostiles.map((_, i) => `mob_${i}`)],
currentTurnIndex: 0,
turnStartTime: Date.now(),
isOver: false,
};
- battle.turnOrder = ["player", ...battle.enemies.map((e) => e.instanceId)];
return battle;
}
calculateEquipmentStats(equipment) {
- const totals = {
- health: 0,
- attack: 0,
- defence: 0,
- resistance: 0,
- };
-
+ const totals = { health: 0, attack: 0, defence: 0, resistance: 0 };
if (!equipment) return totals;
Object.values(equipment).forEach((itemId) => {
if (!itemId) return;
-
const itemData = DatapackLoader.getItem(itemId);
-
if (itemData && itemData.stats) {
Object.entries(itemData.stats).forEach(([key, value]) => {
const lowerKey = key.toLowerCase();
-
- if (lowerKey.includes("health") || lowerKey === "hp") {
+ if (lowerKey.includes("health") || lowerKey === "hp")
totals.health += value;
- } else if (
- lowerKey.includes("attack") ||
- lowerKey.includes("damage") ||
- lowerKey.includes("atk")
- ) {
+ else if (lowerKey.includes("attack") || lowerKey.includes("atk"))
totals.attack += value;
- } else if (
- lowerKey.includes("defence") ||
- lowerKey.includes("armor") ||
- lowerKey.includes("defense")
- ) {
- totals.defence += value;
- } else if (lowerKey.includes("resistance")) {
- totals.resistance += value;
- }
+ else if (lowerKey.includes("def")) totals.defence += value;
+ else if (lowerKey.includes("res")) totals.resistance += value;
});
}
});
-
return totals;
}
@@ -88,38 +79,37 @@ class CombatService {
(e) => e.instanceId === targetInstanceId,
);
if (target && !target.isDead) {
- const damage = battle.player.atk;
- target.hp -= damage;
-
- log.push(`Player dealt ${damage} damage to ${target.name}`);
-
- if (target.hp <= 0) {
- target.hp = 0;
+ const dmg = Math.max(
+ 1,
+ Math.round(battle.player.atk - target.def * 0.5),
+ );
+ target.hp = Math.max(0, target.hp - dmg);
+ log.push(`Commander dealt ${dmg} damage to ${target.name}`);
+ if (target.hp === 0) {
target.isDead = true;
- log.push(`${target.name} destroyed!`);
+ log.push(`${target.name} neutralized.`);
}
}
} else {
const enemy = battle.enemies.find((e) => e.instanceId === attackerId);
- if (enemy && !enemy.isDead) {
- const playerDef = battle.player.stats.defence || 0;
- const finalDamage = Math.max(
+ if (enemy && !enemy.isDead && battle.player.hp > 0) {
+ const dmg = Math.max(
1,
- Math.round(enemy.atk - playerDef * 0.5),
+ Math.round(enemy.atk - battle.player.def * 0.5),
);
-
- battle.player.hp -= finalDamage;
- log.push(`${enemy.name} deals ${finalDamage} damage to Player`);
-
- if (battle.player.hp <= 0) {
- battle.player.hp = 0;
- battle.isOver = true;
- }
+ battle.player.hp = Math.max(0, battle.player.hp - dmg);
+ log.push(`${enemy.name} deals ${dmg} damage to Commander`);
+ if (battle.player.hp === 0) battle.isOver = true;
}
}
-
return log;
}
+
+ handleSkip(battle) {
+ return [
+ `Sequence timeout: ${battle.turnOrder[battle.currentTurnIndex]} skipped turn.`,
+ ];
+ }
}
module.exports = new CombatService();
diff --git a/game-server/src/game/DatapackLoader.js b/game-server/src/game/DatapackLoader.js
index 2a21a8b..ffb1eec 100644
--- a/game-server/src/game/DatapackLoader.js
+++ b/game-server/src/game/DatapackLoader.js
@@ -71,7 +71,7 @@ class DatapackLoader {
});
console.log(
- `🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.quests.size} Quests, ${this.registry.languages.size} Langs, ${manifestCount} Manifests`,
+ `🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.quests.size} Quests, ${this.registry.languages.size} Langs, ${manifestCount} Manifests ${this.registry.rooms.size} Rooms`,
);
}
diff --git a/game-server/src/game/DungeonManager.js b/game-server/src/game/DungeonManager.js
index a92260d..e9e6226 100644
--- a/game-server/src/game/DungeonManager.js
+++ b/game-server/src/game/DungeonManager.js
@@ -1,5 +1,5 @@
-const DatapackLoader = require("./DatapackLoader");
-const CombatService = require("./CombatService");
+const DatapackLoaderRef = require("./DatapackLoader");
+const CombatServiceRef = require("./CombatService");
const QuestsManager = require("./QuestsManager");
const { Player } = require("../models");
@@ -9,12 +9,10 @@ class DungeonManager {
}
async startDungeon(playerId, dungeonId) {
- const dungeon = DatapackLoader.getDungeon(dungeonId);
+ const dungeon = DatapackLoaderRef.getDungeon(dungeonId);
if (!dungeon || !dungeon.rooms?.length) return null;
const player = await Player.findByPk(playerId);
- if (!player) return null;
-
const session = {
playerId,
dungeonId,
@@ -31,12 +29,11 @@ class DungeonManager {
async initRoom(playerId, playerInstance = null) {
const session = this.activeSessions.get(playerId);
if (!session) return null;
-
const roomData = this.getCurrentRoomData(playerId);
const player = playerInstance || (await Player.findByPk(playerId));
if (roomData.hostiles.length > 0) {
- session.battle = CombatService.initializeBattle(
+ session.battle = CombatServiceRef.initializeBattle(
player,
roomData.hostiles,
);
@@ -48,14 +45,11 @@ class DungeonManager {
getCurrentRoomData(playerId) {
const session = this.activeSessions.get(playerId);
- if (!session) return null;
-
- const dungeon = DatapackLoader.getDungeon(session.dungeonId);
+ const dungeon = DatapackLoaderRef.getDungeon(session.dungeonId);
const roomRef = dungeon.rooms[session.currentRoomIndex];
- const rawRoom = DatapackLoader.getRoom(roomRef.id);
-
+ const rawRoom = DatapackLoaderRef.getRoom(roomRef.id);
const hostiles = (rawRoom.hostiles || [])
- .map((hId) => DatapackLoader.getEnemy(hId))
+ .map((hId) => DatapackLoaderRef.getEnemy(hId))
.filter(Boolean);
return {
@@ -71,134 +65,155 @@ class DungeonManager {
if (!session || !session.battle || session.battle.isOver) return null;
const battle = session.battle;
- const log = CombatService.handleAttack(battle, targetInstanceId);
+ if (battle.turnOrder[battle.currentTurnIndex] !== "player") return null;
- const allEnemiesDead = battle.enemies.every((e) => e.isDead);
- const playerDead = battle.player.hp <= 0;
-
- if (playerDead) {
- battle.isOver = true;
- battle.player.hp = 0;
- return {
- battle,
- log: [...log, "CRITICAL_FAILURE: Mission terminated."],
- status: "defeat",
- };
+ let logMessages;
+ if (!targetInstanceId) {
+ logMessages = CombatServiceRef.handleSkip(battle);
+ } else {
+ logMessages = CombatServiceRef.handleAttack(battle, targetInstanceId);
}
- if (allEnemiesDead) {
- battle.isOver = true;
+ const playerAction = {
+ attackerId: "player",
+ messages: logMessages,
+ hpState: this._captureHpState(battle),
+ };
- const roomData = this.getCurrentRoomData(playerId);
- const roomConfig = roomData.config;
+ return this._afterAction(session, [playerAction], socket);
+ }
- session.rewards.xp += roomConfig.gainXp || 0;
- session.rewards.credits += roomConfig.credits || 0;
+ _captureHpState(battle) {
+ return {
+ playerHp: battle.player.hp,
+ enemies: battle.enemies.map((e) => ({
+ id: e.instanceId,
+ hp: e.hp,
+ isDead: e.isDead,
+ })),
+ };
+ }
- if (roomConfig.loot && Array.isArray(roomConfig.loot)) {
- roomConfig.loot.forEach((l) => {
- if (Math.random() <= (l.chance || 1)) {
- let finalCount = 1;
- if (typeof l.count === "object") {
- finalCount =
- Math.floor(Math.random() * (l.count.max - l.count.min + 1)) +
- l.count.min;
- } else if (typeof l.count === "number") {
- finalCount = l.count;
- }
+ _afterAction(session, actionLogs, socket) {
+ const battle = session.battle;
- const existingItem = session.rewards.items.find(
- (i) => i.id === l.id,
- );
- if (existingItem) {
- existingItem.count += finalCount;
- } else {
- session.rewards.items.push({ id: l.id, count: finalCount });
- }
- }
- });
- }
-
- battle.enemies.forEach((enemy) => {
+ battle.enemies.forEach((enemy) => {
+ if (enemy.isDead && !enemy.rewardGiven) {
+ enemy.rewardGiven = true;
session.rewards.xp += enemy.gainXp || 0;
session.rewards.credits += enemy.credits || 0;
+ if (enemy.loot && Array.isArray(enemy.loot)) {
+ const lootMessages = this._distributeLoot(session, enemy.loot);
+ actionLogs.push(...lootMessages);
+ }
+
QuestsManager.trackProgress(
- playerId,
+ session.playerId,
"KILL_ENEMY",
enemy.id,
1,
socket,
);
+ }
+ });
- if (enemy.loot && Array.isArray(enemy.loot)) {
- enemy.loot.forEach((l) => {
- if (Math.random() <= (l.chance || 1)) {
- let finalCount = 1;
- if (typeof l.count === "object") {
- finalCount =
- Math.floor(Math.random() * (l.count.max - l.count.min + 1)) +
- l.count.min;
- } else if (typeof l.count === "number") {
- finalCount = l.count;
- }
+ const allEnemiesDead = battle.enemies.every((e) => e.isDead);
- const existingItem = session.rewards.items.find(
- (i) => i.id === l.id,
- );
- if (existingItem) {
- existingItem.count += finalCount;
- } else {
- session.rewards.items.push({ id: l.id, count: finalCount });
- }
- }
- });
- }
- });
-
- return { battle, log, status: "victory" };
+ if (allEnemiesDead) {
+ battle.isOver = true;
+ const roomConfig = this.getCurrentRoomData(session.playerId).config;
+ if (roomConfig && !roomConfig.rewardGiven) {
+ roomConfig.rewardGiven = true;
+ session.rewards.xp += roomConfig.gainXp || 0;
+ session.rewards.credits += roomConfig.credits || 0;
+ if (roomConfig.loot) this._distributeLoot(session, roomConfig.loot);
+ }
+ return { battle, log: actionLogs, status: "victory" };
}
- return this._nextTurn(session, log);
+ if (battle.player.hp <= 0) {
+ battle.isOver = true;
+ return { battle, log: actionLogs, status: "defeat" };
+ }
+
+ return this._nextTurn(session, actionLogs);
}
- _nextTurn(session, lastLog = []) {
+ _distributeLoot(session, lootTable) {
+ const rewardsLog = [];
+ lootTable.forEach((item) => {
+ if (Math.random() <= (item.chance || 1)) {
+ let finalCount = 0;
+ if (typeof item.count === "object" && item.count !== null) {
+ const min = item.count.min || 0;
+ const max = item.count.max || 1;
+ finalCount = Math.floor(Math.random() * (max - min + 1)) + min;
+ } else {
+ finalCount = Number(item.count) || 1;
+ }
+
+ if (finalCount > 0) {
+ const existing = session.rewards.items.find((i) => i.id === item.id);
+ if (existing) {
+ existing.count += finalCount;
+ } else {
+ session.rewards.items.push({ id: item.id, count: finalCount });
+ }
+ const shortName = item.id.split(":").pop().replace("_", " ");
+ rewardsLog.push(
+ `RECOVERED: ${finalCount}x ${shortName.toUpperCase()}`,
+ );
+ }
+ }
+ });
+ return rewardsLog;
+ }
+
+ _nextTurn(session, accumulatedLogs = []) {
const battle = session.battle;
battle.currentTurnIndex =
(battle.currentTurnIndex + 1) % battle.turnOrder.length;
+ battle.turnStartTime = Date.now();
- const currentEntityId = battle.turnOrder[battle.currentTurnIndex];
- if (currentEntityId !== "player") {
- const enemy = battle.enemies.find(
- (e) => e.instanceId === currentEntityId,
- );
- if (!enemy || enemy.isDead) return this._nextTurn(session, lastLog);
+ const currentId = battle.turnOrder[battle.currentTurnIndex];
+ if (currentId === "player") return { battle, log: accumulatedLogs };
- const enemyLog = CombatService.handleAttack(battle, null);
+ const enemy = battle.enemies.find((e) => e.instanceId === currentId);
+ if (!enemy || enemy.isDead) return this._nextTurn(session, accumulatedLogs);
- if (battle.player.hp <= 0) {
- battle.isOver = true;
- return { battle, log: [...lastLog, ...enemyLog], status: "defeat" };
- }
+ const enemyMessages = CombatServiceRef.handleAttack(battle, null);
+ accumulatedLogs.push({
+ attackerId: currentId,
+ messages: enemyMessages,
+ hpState: this._captureHpState(battle),
+ });
- return this._nextTurn(session, [...lastLog, ...enemyLog]);
+ if (battle.player.hp <= 0 || battle.enemies.every((e) => e.isDead)) {
+ return this._afterAction(session, accumulatedLogs);
}
- return { battle, log: lastLog };
+ return this._nextTurn(session, accumulatedLogs);
}
async moveToNextRoom(playerId) {
const session = this.activeSessions.get(playerId);
- if (!session || session.isFinished) return null;
+ if (!session) return { error: "Session not found" };
- const dungeon = DatapackLoader.getDungeon(session.dungeonId);
- if (session.currentRoomIndex < dungeon.rooms.length - 1) {
- session.currentRoomIndex++;
- return this.initRoom(playerId);
+ if (session.isFinished) {
+ return { status: "completed", rewards: session.rewards };
}
- session.isFinished = true;
- return { status: "completed", rewards: session.rewards };
+ const dungeon = DatapackLoaderRef.getDungeon(session.dungeonId);
+
+ if (session.currentRoomIndex < dungeon.rooms.length - 1) {
+ session.currentRoomIndex++;
+ const newRoomData = await this.initRoom(playerId);
+ return { status: "next_room", ...newRoomData };
+ } else {
+ session.isFinished = true;
+ return { status: "completed", rewards: session.rewards };
+ }
}
leaveDungeon(playerId) {
diff --git a/game-server/src/sockets/handlers/dungeonHandler.js b/game-server/src/sockets/handlers/dungeonHandler.js
index 7cd3714..6126d75 100644
--- a/game-server/src/sockets/handlers/dungeonHandler.js
+++ b/game-server/src/sockets/handlers/dungeonHandler.js
@@ -29,6 +29,7 @@ module.exports = (io, socket) => {
remainingEnergy: player.energy - energyCost,
});
} catch (err) {
+ console.log(err);
socket.emit("error", { message: "Deployment failure" });
}
});