-
-
- {item.displayName || item.name}
-
-
{item.rarity}
+
+
+
+
+
{item.displayName || item.name}
+
+ {item.rarity?.toUpperCase()} SYSTEM_ID: {item.id}
+
+
+
-
{item.description}
+
+
+
+ Technical Specs
+
{item.stats &&
Object.entries(item.stats).map(([statName, value]) => (
@@ -43,7 +66,9 @@ const ItemModal = ({
))}
+
+
{isEquipped ? (
) : (
item.canEquip && (
diff --git a/client/src/views/GameInterface/tabs/styles/ChatTab.css b/client/src/views/GameInterface/tabs/styles/ChatTab.css
index cea5596..f8ee1aa 100644
--- a/client/src/views/GameInterface/tabs/styles/ChatTab.css
+++ b/client/src/views/GameInterface/tabs/styles/ChatTab.css
@@ -194,6 +194,9 @@
.message {
font-size: 13px;
line-height: 1.4;
+ min-height: min-content;
+ padding: 4px 0;
+ display: block;
}
.msg-time {
@@ -214,7 +217,12 @@
.message.system .msg-text {
color: #888;
+ word-break: break-all;
+ overflow-wrap: anywhere;
+ display: inline-block;
font-style: italic;
+ white-space: pre-wrap;
+ max-width: 100%;
}
.chat-input-area {
@@ -411,3 +419,29 @@
color: #ff3e3e;
text-shadow: 0 0 8px rgba(255, 62, 62, 0.4);
}
+
+.message {
+ font-size: 13px;
+ line-height: 1.4;
+ min-height: min-content;
+ padding: 4px 0;
+ display: block;
+ word-wrap: break-word;
+}
+
+.msg-text {
+ color: #fff;
+ word-break: break-all;
+ overflow-wrap: anywhere;
+ white-space: pre-wrap;
+ display: inline;
+}
+
+.message.system .msg-author {
+ color: #ff3e3e;
+}
+
+.message.system .msg-text {
+ color: #888;
+ font-style: italic;
+}
diff --git a/client/src/views/GameInterface/tabs/styles/DungeonsTab.css b/client/src/views/GameInterface/tabs/styles/DungeonsTab.css
index 78d8625..18419d0 100644
--- a/client/src/views/GameInterface/tabs/styles/DungeonsTab.css
+++ b/client/src/views/GameInterface/tabs/styles/DungeonsTab.css
@@ -1,4 +1,3 @@
-/* Контейнер вкладки */
#dungeons-tab {
height: 100%;
background: #05080c;
@@ -13,7 +12,6 @@
border-top: 1px solid rgba(0, 212, 255, 0.2);
}
-/* --- Ліва панель --- */
.dungeon-selector {
border-right: 1px solid rgba(0, 212, 255, 0.1);
display: flex;
@@ -96,7 +94,6 @@
font-family: "Space Mono", monospace;
}
-/* --- Права панель --- */
.dungeon-view {
padding: 20px;
display: flex;
@@ -119,6 +116,18 @@
background: rgba(0, 212, 255, 0.03);
position: relative;
overflow: hidden;
+ display: flex;
+ align-items: center;
+ gap: 15px;
+}
+
+.back-to-list {
+ display: none;
+ background: none;
+ border: 1px solid #00d4ff;
+ color: #00d4ff;
+ padding: 8px 12px;
+ cursor: pointer;
}
.scanline-horizontal {
@@ -174,11 +183,6 @@
line-height: 1.6;
}
-/* --- Нагороди --- */
-.expected-rewards-section {
- margin-bottom: 30px;
-}
-
.section-header {
display: flex;
align-items: center;
@@ -195,7 +199,7 @@
.rewards-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
@@ -209,14 +213,15 @@
}
.reward-icon-container {
- width: 40px;
- height: 40px;
+ width: 36px;
+ height: 36px;
background: #0a0f18;
border: 1px solid rgba(0, 212, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
- margin-right: 15px;
+ margin-right: 12px;
+ flex-shrink: 0;
}
.reward-icon-container img {
@@ -225,29 +230,27 @@
object-fit: contain;
}
-.reward-icon-container i {
- color: #4a5d75;
- font-size: 1.2rem;
-}
-
.reward-text {
display: flex;
flex-direction: column;
+ overflow: hidden;
}
.reward-name {
- font-size: 0.85rem;
+ font-size: 0.75rem;
color: #fff;
text-transform: uppercase;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.reward-chance {
- font-size: 0.7rem;
+ font-size: 0.65rem;
color: #00ff88;
font-family: "Space Mono", monospace;
}
-/* Кольори раритетності */
.reward-entry.common {
border-left-color: #4a5d75;
}
@@ -264,20 +267,15 @@
border-left-color: #ffaa00;
}
-/* --- Кнопка --- */
-.action-area {
- margin-top: 20px;
-}
-
.initiate-deployment-btn {
width: 100%;
- padding: 20px;
+ padding: 18px;
background: #00d4ff;
border: none;
color: #000;
font-family: "Orbitron", sans-serif;
font-weight: 900;
- font-size: 1.1rem;
+ font-size: 1rem;
cursor: pointer;
display: flex;
justify-content: space-between;
@@ -288,15 +286,9 @@
.initiate-deployment-btn:hover {
background: #fff;
- box-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
- transform: translateY(-2px);
+ box-shadow: 0 0 20px rgba(0, 212, 255, 0.4);
}
-.initiate-deployment-btn i {
- font-size: 1.2rem;
-}
-
-/* --- Placeholder --- */
.dungeon-placeholder {
height: 100%;
display: flex;
@@ -307,8 +299,8 @@
}
.radar-scanner {
- width: 100px;
- height: 100px;
+ width: 80px;
+ height: 80px;
border: 2px solid rgba(0, 212, 255, 0.2);
border-radius: 50%;
position: relative;
@@ -336,17 +328,43 @@
}
}
-/* Кастомний скрол */
+@media screen and (max-width: 768px) {
+ .dungeons-container {
+ grid-template-columns: 1fr;
+ }
+
+ .dungeons-container.view-active .dungeon-selector {
+ display: none;
+ }
+
+ .dungeons-container:not(.view-active) .dungeon-view {
+ display: none;
+ }
+
+ .back-to-list {
+ display: block;
+ }
+
+ .mission-title {
+ font-size: 1.3rem;
+ }
+
+ .dungeon-view {
+ padding: 10px;
+ }
+
+ .rewards-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
.custom-scroll::-webkit-scrollbar {
- width: 5px;
+ width: 4px;
}
.custom-scroll::-webkit-scrollbar-track {
- background: rgba(0, 0, 0, 0.2);
+ background: rgba(0, 0, 0, 0.1);
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #1a2638;
- border-radius: 10px;
-}
-.custom-scroll::-webkit-scrollbar-thumb:hover {
- background: #00d4ff;
+ border-radius: 4px;
}
diff --git a/client/src/views/GameInterface/tabs/styles/InventoryTab.css b/client/src/views/GameInterface/tabs/styles/InventoryTab.css
index 9243ec2..eb07ac6 100644
--- a/client/src/views/GameInterface/tabs/styles/InventoryTab.css
+++ b/client/src/views/GameInterface/tabs/styles/InventoryTab.css
@@ -233,3 +233,41 @@
border-radius: 3px;
z-index: 2;
}
+
+.qty-label {
+ position: absolute;
+ bottom: 2px;
+ right: 4px;
+ font-size: 11px;
+ font-weight: 800;
+ color: #fff;
+ text-shadow:
+ 1px 1px 0 #000,
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 0 0 5px rgba(0, 0, 0, 0.8);
+ pointer-events: none;
+ z-index: 3;
+}
+
+.item-slot {
+ width: 60px;
+ height: 60px;
+ background: rgba(5, 8, 12, 0.9);
+ border: 1px solid #1a2638;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ cursor: pointer;
+ transition: 0.2s;
+ overflow: visible;
+}
+
+.item-img-grid {
+ max-width: 80%;
+ max-height: 80%;
+ object-fit: contain;
+ pointer-events: none;
+}
diff --git a/game-server/datapacks/core/assets/audio/GSO-Button Sound.wav b/game-server/datapacks/core/assets/audio/GSO-Button Sound.wav
new file mode 100644
index 0000000..20b3381
Binary files /dev/null and b/game-server/datapacks/core/assets/audio/GSO-Button Sound.wav differ
diff --git a/game-server/datapacks/core/assets/audio/GSO-Button Sound.webm b/game-server/datapacks/core/assets/audio/GSO-Button Sound.webm
new file mode 100644
index 0000000..8229a15
Binary files /dev/null and b/game-server/datapacks/core/assets/audio/GSO-Button Sound.webm differ
diff --git a/game-server/datapacks/core/assets/audio/GSO-Space ship Engine Idle.mp4 b/game-server/datapacks/core/assets/audio/GSO-Space ship Engine Idle.mp4
new file mode 100644
index 0000000..9ce3c2d
Binary files /dev/null and b/game-server/datapacks/core/assets/audio/GSO-Space ship Engine Idle.mp4 differ
diff --git a/game-server/datapacks/core/assets/audio/GSO-Space ship Engine Idle.webm b/game-server/datapacks/core/assets/audio/GSO-Space ship Engine Idle.webm
new file mode 100644
index 0000000..483bc61
Binary files /dev/null and b/game-server/datapacks/core/assets/audio/GSO-Space ship Engine Idle.webm differ
diff --git a/game-server/datapacks/original/assets/textures/materials/circuits/advanced_circut.png b/game-server/datapacks/original/assets/textures/materials/circuits/advanced_circuit.png
similarity index 100%
rename from game-server/datapacks/original/assets/textures/materials/circuits/advanced_circut.png
rename to game-server/datapacks/original/assets/textures/materials/circuits/advanced_circuit.png
diff --git a/game-server/datapacks/original/assets/textures/materials/circuits/basic_circut.png b/game-server/datapacks/original/assets/textures/materials/circuits/basic_circuit.png
similarity index 100%
rename from game-server/datapacks/original/assets/textures/materials/circuits/basic_circut.png
rename to game-server/datapacks/original/assets/textures/materials/circuits/basic_circuit.png
diff --git a/game-server/datapacks/original/data/dungeons/tutorial/tutorial.json b/game-server/datapacks/original/data/dungeons/tutorial/tutorial.json
index 66ae565..4e81896 100644
--- a/game-server/datapacks/original/data/dungeons/tutorial/tutorial.json
+++ b/game-server/datapacks/original/data/dungeons/tutorial/tutorial.json
@@ -3,12 +3,14 @@
"id": "original:tutorial/tutorial_dungeon",
"displayName": "dungeons.original.tutorial.tutorial",
"description": "dungeons.original.tutorial.tutorial.desc",
- "meta":{
+ "meta": {
"energyCost": 0,
"repeatable": false,
- "missionArea":"space",
- "raid": false, "_comment_1":"Future raid type picking, when you can have friends in the dugeon to help you.",
- "missionAllowed": [], "_comment_2":"Future ship type picking, when ship classes are started"
+ "missionArea": "space",
+ "raid": false,
+ "_comment_1": "Future raid type picking, when you can have friends in the dugeon to help you.",
+ "missionAllowed": [],
+ "_comment_2": "Future ship type picking, when ship classes are started"
},
"rooms": [
{ "id": "original:tutorial/tutorial_enemy_room" },
diff --git a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json
index 3863b07..908825c 100644
--- a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json
+++ b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json
@@ -8,6 +8,19 @@
"damage": 4,
"critical.chance": 0.3,
"attack.rate": 2
- }
+ },
+ "loot": [
+ {
+ "id": "original:ore_coal",
+ "chance": 1.0,
+ "count": 50
+ },
+ {
+ "id": "original:ore_copper",
+ "chance": 1.0,
+ "count": 20
+ }
+ ],
+ "meta": {}
}
}
diff --git a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json
index 6679335..6be80d6 100644
--- a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json
+++ b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json
@@ -6,8 +6,19 @@
"health": 30,
"defense": 0.0,
"damage": 2,
- "critical,chance": 0.0,
+ "critical.chance": 0.0,
"attack.rate": 1
- }
+ },
+ "loot": [
+ {
+ "id": "original:alloy_steel",
+ "chance": 0.4,
+ "count": {
+ "min": 1,
+ "max": 2
+ }
+ }
+ ],
+ "meta": {}
}
}
diff --git a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_boss.json b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_boss.json
index f60ef65..dcb1b2d 100644
--- a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_boss.json
+++ b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_boss.json
@@ -1,8 +1,14 @@
{
"rooms": {
"id": "original:tutorial/tutorial_boss_room",
+ "displayName": "rooms.original.tutorial.tutorial_boss_room.name",
+ "description": "rooms.original.tutorial.tutorial_boss_room.desc",
"hostiles": ["original:tutorial/tutorial_boss_hostile"],
"gainXp": 4,
- "credits": 200
+ "credits": 200,
+ "loot": [],
+ "meta": {
+ "isBossRoom": true
+ }
}
}
diff --git a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json
index 04c3a93..1234a19 100644
--- a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json
+++ b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json
@@ -1,8 +1,14 @@
{
"rooms": {
"id": "original:tutorial/tutorial_enemy_room",
+ "displayName": "rooms.original.tutorial.tutorial_enemy_room.name",
+ "description": "rooms.original.tutorial.tutorial_enemy_room.desc",
"hostiles": ["original:tutorial/tutorial_hostile"],
"gainXp": 3,
- "credits": 30
+ "credits": 30,
+ "loot": [],
+ "meta": {
+ "isBossRoom": false
+ }
}
}
diff --git a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_loot_room.json b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_loot_room.json
index 38580dd..6a7812b 100644
--- a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_loot_room.json
+++ b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_loot_room.json
@@ -1,6 +1,20 @@
{
"rooms": {
"id": "original:tutorial/tutorial_loot_room",
- "loot": [{ "id": "original:bio_pulp", "count": 1 }]
+ "displayName": "rooms.original.tutorial.tutorial_loot_room.name",
+ "description": "rooms.original.tutorial.tutorial_loot_room.desc",
+ "hostiles": [],
+ "gainXp": 0,
+ "credits": 0,
+ "loot": [
+ {
+ "id": "original:bio_pulp",
+ "chance": 1.0,
+ "count": 1
+ }
+ ],
+ "meta": {
+ "isBossRoom": false
+ }
}
}
diff --git a/game-server/datapacks/original/data/items/materials/circuits/advanced_circuit.json b/game-server/datapacks/original/data/items/materials/circuits/advanced_circuit.json
index 2076724..5209ec7 100644
--- a/game-server/datapacks/original/data/items/materials/circuits/advanced_circuit.json
+++ b/game-server/datapacks/original/data/items/materials/circuits/advanced_circuit.json
@@ -1,11 +1,16 @@
{
"materials": {
"id": "original:circuit_advanced",
- "texture": "test/assets/textures/materials/circuits/advanced_circuit.png",
+ "texture": "original/assets/textures/materials/circuits/advanced_circuit.png",
"displayName": "items.materials.original.circuits.advanced",
"description": "items.materials.original.circuits.advanced.desc",
"meta": {
- "storeCategory": "original:materials"
+ "storeCategory": "original:materials",
+ "storePrice": 50,
+ "storeSellValue": 10,
+ "storeShowWeight": 10,
+ "storeFeaturedDiscountPercentage": 0,
+ "storeFeaturedShowWeight": 10
}
}
}
\ No newline at end of file
diff --git a/game-server/datapacks/original/data/items/materials/circuits/ai_core.json b/game-server/datapacks/original/data/items/materials/circuits/ai_core.json
index 4e38ef6..1010f84 100644
--- a/game-server/datapacks/original/data/items/materials/circuits/ai_core.json
+++ b/game-server/datapacks/original/data/items/materials/circuits/ai_core.json
@@ -1,11 +1,16 @@
{
"materials": {
"id": "original:circuit_ai_core",
- "texture": "test/assets/textures/materials/circuits/ai_core.gif",
+ "texture": "original/assets/textures/materials/circuits/ai_core.gif",
"displayName": "items.materials.original.circuits.ai_core",
"description": "items.materials.original.circuits.ai_core.desc",
"meta": {
- "storeCategory": "original:materials"
+ "storeCategory": "original:materials",
+ "storePrice": 50,
+ "storeSellValue": 10,
+ "storeShowWeight": 10,
+ "storeFeaturedDiscountPercentage": 0,
+ "storeFeaturedShowWeight": 10
}
}
}
\ No newline at end of file
diff --git a/game-server/datapacks/original/data/items/materials/circuits/basic_circuit.json b/game-server/datapacks/original/data/items/materials/circuits/basic_circuit.json
index 32754d4..a03d810 100644
--- a/game-server/datapacks/original/data/items/materials/circuits/basic_circuit.json
+++ b/game-server/datapacks/original/data/items/materials/circuits/basic_circuit.json
@@ -1,11 +1,16 @@
{
"materials": {
"id": "original:circuit_basic",
- "texture": "test/assets/textures/materials/circuits/basic_circuit.png",
+ "texture": "original/assets/textures/materials/circuits/basic_circuit.png",
"displayName": "items.materials.original.circuits.basic",
"description": "items.materials.original.circuits.basic.desc",
"meta": {
- "storeCategory": "original:materials"
+ "storeCategory": "original:materials",
+ "storePrice": 50,
+ "storeSellValue": 10,
+ "storeShowWeight": 10,
+ "storeFeaturedDiscountPercentage": 0,
+ "storeFeaturedShowWeight": 10
}
}
}
\ No newline at end of file
diff --git a/game-server/datapacks/original/data/items/materials/circuits/processing_unit.json b/game-server/datapacks/original/data/items/materials/circuits/processing_unit.json
index 9e4df17..d4ab629 100644
--- a/game-server/datapacks/original/data/items/materials/circuits/processing_unit.json
+++ b/game-server/datapacks/original/data/items/materials/circuits/processing_unit.json
@@ -1,11 +1,16 @@
{
"materials": {
"id": "original:circuit_processing_unit",
- "texture": "test/assets/textures/materials/circuits/processing_unit.png",
+ "texture": "original/assets/textures/materials/circuits/processing_unit.png",
"displayName": "items.materials.original.circuits.processing_unit",
"description": "items.materials.original.circuits.processing_unit.desc",
"meta": {
- "storeCategory": "original:materials"
+ "storeCategory": "original:materials",
+ "storePrice": 50,
+ "storeSellValue": 10,
+ "storeShowWeight": 10,
+ "storeFeaturedDiscountPercentage": 0,
+ "storeFeaturedShowWeight": 10
}
}
}
\ No newline at end of file
diff --git a/game-server/datapacks/original/data/items/materials/circuits/quantum_processor.json b/game-server/datapacks/original/data/items/materials/circuits/quantum_processor.json
index 7b6ff90..a3e7377 100644
--- a/game-server/datapacks/original/data/items/materials/circuits/quantum_processor.json
+++ b/game-server/datapacks/original/data/items/materials/circuits/quantum_processor.json
@@ -1,11 +1,16 @@
{
"materials": {
"id": "original:circuit_quantum_processor",
- "texture": "test/assets/textures/materials/circuits/quantum_processor.png",
+ "texture": "original/assets/textures/materials/circuits/quantum_processor.png",
"displayName": "items.materials.original.circuits.quantum_processor",
"description": "items.materials.original.circuits.quantum_processor.desc",
"meta": {
- "storeCategory": "original:materials"
+ "storeCategory": "original:materials",
+ "storePrice": 50,
+ "storeSellValue": 10,
+ "storeShowWeight": 10,
+ "storeFeaturedDiscountPercentage": 0,
+ "storeFeaturedShowWeight": 10
}
}
}
\ No newline at end of file
diff --git a/game-server/src/game/DatapackLoader.js b/game-server/src/game/DatapackLoader.js
index 0253602..55a868f 100644
--- a/game-server/src/game/DatapackLoader.js
+++ b/game-server/src/game/DatapackLoader.js
@@ -147,7 +147,7 @@ class DatapackLoader {
const data = json[typeKey];
if (!data) return;
- const fullId = `${packName}:${data.id}`;
+ const fullId = `${data.id}`;
switch (typeKey) {
case "armour":
diff --git a/game-server/src/game/DungeonManager.js b/game-server/src/game/DungeonManager.js
index b4d741c..4dca3a7 100644
--- a/game-server/src/game/DungeonManager.js
+++ b/game-server/src/game/DungeonManager.js
@@ -16,7 +16,6 @@ class DungeonManager {
currentEnemyHp: undefined,
rewards: { xp: 0, credits: 0, items: [] },
};
-
this.activeSessions.set(playerId, session);
return this.getCurrentRoomData(playerId);
}
@@ -27,55 +26,103 @@ class DungeonManager {
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
const roomRef = dungeon.rooms[session.currentRoomIndex];
- const roomData = DatapackLoader.getRoom(roomRef.id);
- if (!roomData) return null;
+ const rawRoom = DatapackLoader.getRoom(roomRef.id);
- const hostiles = (roomData.hostiles || [])
- .map((hId) => {
- const hostile = DatapackLoader.getEnemy(hId);
- return hostile ? { ...hostile } : null;
- })
+ if (!rawRoom) return null;
+
+ const hostiles = (rawRoom.hostiles || [])
+ .map((hId) => DatapackLoader.getEnemy(hId))
.filter(Boolean);
return {
roomIndex: session.currentRoomIndex,
totalRooms: dungeon.rooms.length,
- config: roomData,
+ config: rawRoom,
hostiles,
};
}
+ processCombatStep(playerId, enemyId) {
+ const session = this.activeSessions.get(playerId);
+ if (!session || session.isFinished) return null;
+
+ const enemy = DatapackLoader.getEnemy(enemyId);
+ if (!enemy) return null;
+
+ if (session.currentEnemyHp === undefined) {
+ session.currentEnemyHp = enemy.stats?.health || 100;
+ }
+
+ const damage = Math.floor(Math.random() * 10) + 20;
+ session.currentEnemyHp -= damage;
+
+ const isDefeated = session.currentEnemyHp <= 0;
+ let lootDropped = [];
+
+ if (isDefeated) {
+ if (enemy.loot) {
+ lootDropped = this._generateLoot(enemy.loot);
+ session.rewards.items.push(...lootDropped);
+ }
+ session.currentEnemyHp = undefined;
+ }
+
+ return {
+ damageDealt: damage,
+ enemyHp: Math.max(0, session.currentEnemyHp || 0),
+ targetDefeated: isDefeated,
+ loot: lootDropped,
+ };
+ }
+
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);
+ const rawRoom = 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 });
+ if (rawRoom) {
+ if (rawRoom.hostiles) {
+ rawRoom.hostiles.forEach((hId) => {
+ const enemy = DatapackLoader.getEnemy(hId);
+ if (enemy) {
+ session.rewards.xp += enemy.gainXp || 0;
+ session.rewards.credits += enemy.credits || 0;
+ }
});
}
+ session.rewards.xp += rawRoom.gainXp || 0;
+ session.rewards.credits += rawRoom.credits || 0;
+ if (rawRoom.loot) {
+ session.rewards.items.push(...this._generateLoot(rawRoom.loot));
+ }
}
- 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,
- };
+ return { status: "completed", rewards: session.rewards };
+ }
+
+ _generateLoot(lootTable) {
+ const dropped = [];
+ lootTable.forEach((entry) => {
+ if (Math.random() <= (entry.chance || 1.0)) {
+ const count =
+ typeof entry.count === "object"
+ ? Math.floor(
+ Math.random() * (entry.count.max - entry.count.min + 1),
+ ) + entry.count.min
+ : entry.count || 1;
+ dropped.push({ id: entry.id, count });
+ }
+ });
+ return dropped;
}
leaveDungeon(playerId) {
diff --git a/game-server/src/sockets/handlers/dungeonHandler.js b/game-server/src/sockets/handlers/dungeonHandler.js
index a3e0934..c1ec67a 100644
--- a/game-server/src/sockets/handlers/dungeonHandler.js
+++ b/game-server/src/sockets/handlers/dungeonHandler.js
@@ -9,25 +9,16 @@ module.exports = (io, socket) => {
try {
if (!userId) return;
const dungeon = DatapackLoader.getDungeon(dungeonId);
- if (!dungeon) {
- return socket.emit("error", { message: "Dungeon not found" });
- }
-
const player = await Player.findByPk(userId);
- const energyCost = dungeon.meta?.energyCost || 0;
+ const energyCost = dungeon?.meta?.energyCost || 0;
- if (player.energy < energyCost) {
+ if (!dungeon)
+ return socket.emit("error", { message: "Dungeon not found" });
+ 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: "Failed to initialize dungeon",
- });
- }
socket.emit("dungeon:started", {
dungeonId: dungeon.id,
@@ -38,69 +29,34 @@ module.exports = (io, socket) => {
remainingEnergy: player.energy - energyCost,
});
} catch (err) {
- console.error("Dungeon Start Error:", err);
socket.emit("error", { message: "Critical deployment failure" });
}
});
socket.on("dungeon:combat_step", async ({ enemyId }) => {
- try {
- if (!userId) return;
+ const result = dungeonManager.processCombatStep(userId, enemyId);
+ if (!result) return;
- const session = dungeonManager.activeSessions.get(userId);
- if (!session || session.isFinished) return;
-
- const enemyTemplate = DatapackLoader.getEnemy(enemyId);
- if (!enemyTemplate) {
- return socket.emit("error", { message: "Target data corrupted" });
- }
-
- if (session.currentEnemyHp === undefined) {
- session.currentEnemyHp = enemyTemplate.stats?.health || 100;
- }
-
- const damage = Math.floor(Math.random() * 10) + 20;
- session.currentEnemyHp -= damage;
-
- const isDefeated = session.currentEnemyHp <= 0;
-
- socket.emit("dungeon:combat_result", {
- damageDealt: damage,
- enemyHp: Math.max(0, session.currentEnemyHp),
- targetDefeated: isDefeated,
- message: `Strike successful. Dealt ${damage} damage.`,
- });
-
- if (isDefeated) {
- session.currentEnemyHp = undefined;
- }
- } catch (err) {
- console.error("Combat Error:", err);
- }
+ socket.emit("dungeon:combat_result", {
+ ...result,
+ message: result.targetDefeated
+ ? "Enemy eliminated!"
+ : `Strike successful. Dealt ${result.damageDealt} damage.`,
+ });
});
socket.on("dungeon:next_room", async () => {
try {
- if (!userId) return;
-
const nextRoom = dungeonManager.moveToNextRoom(userId);
- if (!nextRoom) {
- return socket.emit("error", {
- message: "Could not proceed to next room",
- });
- }
+ if (!nextRoom)
+ return socket.emit("error", { message: "Navigation error" });
if (nextRoom.status === "completed") {
await finalizeDungeon(socket, nextRoom.rewards);
} else {
- socket.emit("dungeon:room_update", {
- room: nextRoom.config,
- hostiles: nextRoom.hostiles,
- roomIndex: nextRoom.roomIndex,
- });
+ socket.emit("dungeon:room_update", nextRoom);
}
} catch (err) {
- console.error("Dungeon Progress Error:", err);
socket.emit("error", { message: "Navigation system error" });
}
});
@@ -115,14 +71,33 @@ async function finalizeDungeon(socket, sessionRewards) {
try {
const player = await Player.findByPk(userId);
- if (sessionRewards.credits > 0) {
+ if (sessionRewards.credits > 0)
await player.increment("credits", { by: sessionRewards.credits });
+ if (sessionRewards.xp > 0)
+ await player.increment("experience", { by: sessionRewards.xp });
+
+ if (sessionRewards.items.length > 0) {
+ const consolidated = sessionRewards.items.reduce((acc, curr) => {
+ acc[curr.id] = (acc[curr.id] || 0) + curr.count;
+ return acc;
+ }, {});
+
+ for (const [itemId, totalCount] of Object.entries(consolidated)) {
+ const [invItem] = await Inventory.findOrCreate({
+ where: { playerId: userId, itemId: itemId },
+ defaults: { quantity: 0 },
+ });
+ await invItem.increment("quantity", { by: totalCount });
+ }
+
+ sessionRewards.items = Object.entries(consolidated).map(
+ ([id, count]) => ({ id, count }),
+ );
}
- socket.emit("dungeon:completed", {
- rewards: sessionRewards,
- message: "Mission successful. All objectives secured.",
- });
+ socket.emit("dungeon:completed", { rewards: sessionRewards });
+ } catch (err) {
+ socket.emit("error", { message: "Failed to save rewards" });
} finally {
dungeonManager.leaveDungeon(userId);
}