diff --git a/client/src/services/GameDataManager.js b/client/src/services/GameDataManager.js index 9f3a987..a23f486 100644 --- a/client/src/services/GameDataManager.js +++ b/client/src/services/GameDataManager.js @@ -31,7 +31,6 @@ class GameDataManager { if (Array.isArray(data.rooms)) { data.rooms.forEach((r) => this.rooms.set(r.id, r)); } - if (data.languages) { this.translations = data.languages; } diff --git a/client/src/views/GameInterface/components/DungeonScreen.css b/client/src/views/GameInterface/components/DungeonScreen.css index 8dc10cd..401a82d 100644 --- a/client/src/views/GameInterface/components/DungeonScreen.css +++ b/client/src/views/GameInterface/components/DungeonScreen.css @@ -420,3 +420,439 @@ opacity: 0.4; font-size: 0.6rem; } + +.dungeon-active-screen { + display: flex; + flex-direction: column; + height: 100vh; + background: radial-gradient(circle at center, #0a1118 0%, #05080c 100%); + padding: 30px; + gap: 25px; + font-family: "Space Mono", monospace; + color: #e0e6ed; + box-sizing: border-box; + overflow: hidden; +} + +.dungeon-header { + display: flex; + justify-content: space-between; + align-items: flex-end; + border-bottom: 1px solid rgba(0, 212, 255, 0.3); + padding-bottom: 15px; + position: relative; + flex-shrink: 0; +} + +.dungeon-header::after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + width: 60px; + height: 3px; + background: #00d4ff; + box-shadow: 0 0 15px #00d4ff; +} + +.progress-text { + font-size: 0.75rem; + letter-spacing: 2px; + color: #00d4ff; + font-weight: bold; +} + +.progress-bar { + width: 250px; + height: 6px; + background: rgba(255, 255, 255, 0.05); + margin-top: 8px; + border-radius: 3px; + overflow: hidden; +} + +.progress-bar .fill { + height: 100%; + background: linear-gradient(90deg, #00d4ff, #0088ff); + box-shadow: 0 0 10px rgba(0, 212, 255, 0.5); + transition: width 0.5s ease-in-out; +} + +.dungeon-title-area { + text-align: right; +} + +.dungeon-name { + font-family: "Orbitron", sans-serif; + font-size: 1.4rem; + font-weight: 900; + letter-spacing: 1px; + text-transform: uppercase; + color: #fff; +} + +.dungeon-status-tag { + font-size: 0.7rem; + color: #ff4444; + animation: blink 1.5s infinite; +} + +.battle-layout { + display: grid; + grid-template-columns: 1.2fr 1fr; + flex: 1; + gap: 30px; + min-height: 0; +} + +.enemy-display { + position: relative; + height: 100%; + min-height: 0; +} + +.enemy-card { + background: linear-gradient( + 180deg, + rgba(255, 68, 68, 0.08) 0%, + rgba(0, 0, 0, 0) 100% + ); + border: 1px solid rgba(255, 68, 68, 0.2); + border-radius: 4px; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.enemy-card.defeated { + filter: grayscale(1) brightness(0.5); + border-color: rgba(160, 172, 186, 0.2); + background: rgba(0, 0, 0, 0.4); +} + +.threat-tag { + color: #ff4444; + font-family: "Orbitron", sans-serif; + font-size: 0.8rem; + letter-spacing: 4px; + margin-bottom: 30px; + text-shadow: 0 0 10px rgba(255, 68, 68, 0.5); +} + +.enemy-icon { + font-size: 6rem; + color: #fff; + margin-bottom: 25px; + filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.2)); +} + +.enemy-name { + font-family: "Orbitron", sans-serif; + font-size: 1.8rem; + margin: 0 0 20px 0; + color: #fff; + text-transform: uppercase; +} + +.enemy-hp-container { + width: 60%; + margin-bottom: 30px; +} + +.hp-label { + font-size: 0.6rem; + color: #a0acba; + margin-bottom: 6px; + text-align: center; + letter-spacing: 1px; +} + +.hp-bar-mini { + height: 8px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 2px; +} + +.hp-fill-mini { + height: 100%; + background: #ff4444; + box-shadow: 0 0 15px rgba(255, 68, 68, 0.6); + transition: width 0.3s ease-out; +} + +.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 { + background: rgba(26, 38, 56, 0.5); + padding: 10px 15px; + font-size: 0.7rem; + font-weight: bold; + letter-spacing: 1px; + color: #00d4ff; + border-bottom: 1px solid rgba(26, 38, 56, 0.8); + flex-shrink: 0; +} + +.combat-log { + flex: 1; + padding: 20px; + font-size: 0.8rem; + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + scrollbar-width: thin; + min-height: 0; +} + +.log-entry { + line-height: 1.4; + color: #a0acba; + word-break: break-all; +} + +.log-arrow { + color: #00d4ff; + margin-right: 8px; + font-weight: bold; +} + +.dungeon-controls { + display: flex; + gap: 20px; + height: 70px; + flex-shrink: 0; +} + +.ctrl-btn { + flex: 1; + border: none; + font-family: "Orbitron", sans-serif; + font-weight: 900; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + font-size: 1rem; + letter-spacing: 2px; + clip-path: polygon(10px 0%, 100% 0%, calc(100% - 10px) 100%, 0% 100%); + transition: all 0.2s; +} + +.ctrl-btn.combat { + background: #ff4444; + color: #000; +} +.ctrl-btn.loot { + background: #ffaa00; + color: #000; +} +.ctrl-btn.next { + background: #00d4ff; + color: #000; +} + +.environment-panel { + background: rgba(0, 20, 40, 0.6); + border: 1px solid rgba(0, 255, 255, 0.2); + padding: 12px; + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +.env-info { + display: flex; + align-items: center; + gap: 12px; +} + +@media screen and (max-width: 768px) { + .dungeon-active-screen { + padding: 15px; + gap: 15px; + height: 100%; + overflow-y: auto; + } + + .dungeon-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .progress-bar { + width: 100%; + } + + .dungeon-title-area { + text-align: left; + width: 100%; + } + + .battle-layout { + grid-template-columns: 1fr; + display: flex; + flex-direction: column; + gap: 15px; + flex: none; + } + + .enemy-display { + min-height: 300px; + } + + .enemy-icon { + font-size: 4rem; + } + + .enemy-name { + font-size: 1.3rem; + } + + .combat-log-wrapper { + min-width: 100%; + max-height: 250px; + } + + .dungeon-controls { + height: 60px; + position: sticky; + bottom: 0; + background: #05080c; + padding-top: 10px; + margin-top: auto; + } + + .ctrl-btn { + font-size: 0.8rem; + letter-spacing: 1px; + } +} + +@media screen and (max-width: 480px) { + .enemy-display { + min-height: 260px; + } + + .enemy-hp-container { + width: 90%; + } + + .ctrl-btn { + gap: 5px; + font-size: 0.75rem; + } +} + +.enemy-info-footer { + display: flex; + align-items: center; + justify-content: space-between; + width: 90%; + margin-top: auto; + padding-bottom: 15px; + gap: 10px; +} + +.card-id { + opacity: 0.4; + font-size: 0.6rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; +} + +.enemy-info-footer span:first-child { + white-space: nowrap;/ +} + + +@media screen and (max-width: 768px) { + .enemy-display { + min-height: 220px; + flex: 0 0 auto; + } + + .enemy-card { + padding: 10px; + } + + .enemy-icon { + font-size: 3rem; + margin-bottom: 10px; + } + + .enemy-name { + font-size: 1.1rem; + margin-bottom: 10px; + } + + .threat-tag { + margin-bottom: 10px; + font-size: 0.65rem; + letter-spacing: 2px; + } + + .enemy-hp-container { + margin-bottom: 15px; + } + + .combat-log-wrapper { + max-height: 180px; + } + + .log-header { + padding: 6px 12px; + font-size: 0.6rem; + } + + .combat-log { + padding: 10px; + gap: 4px; + } + + .log-entry { + font-size: 0.7rem; + line-height: 1.2; + } + + .log-arrow { + margin-right: 4px; + } + + .enemy-info-footer { + padding-bottom: 10px; + font-size: 0.6rem; + } +} + +@media screen and (max-width: 400px) { + .enemy-display { + min-height: 190px; + } + + .enemy-icon { + font-size: 2.5rem; + } + + .combat-log-wrapper { + max-height: 150px; + } +} diff --git a/client/src/views/GameInterface/components/DungeonScreen.jsx b/client/src/views/GameInterface/components/DungeonScreen.jsx index cc9600f..a4dd728 100644 --- a/client/src/views/GameInterface/components/DungeonScreen.jsx +++ b/client/src/views/GameInterface/components/DungeonScreen.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef } from "react"; import GameDataManager from "../../../services/GameDataManager.js"; import "./DungeonScreen.css"; +import DungeonFinish from "../tabs/components/DungeonFinish.jsx"; const DungeonScreen = ({ session, socket }) => { const [roomData, setRoomData] = useState(session.room); @@ -9,6 +10,7 @@ const DungeonScreen = ({ session, socket }) => { const [enemyHp, setEnemyHp] = useState(null); const [isEnemyDefeated, setIsEnemyDefeated] = useState(false); const [isLooted, setIsLooted] = useState(false); + const [summary, setSummary] = useState(null); const [log, setLog] = useState([ "SYSTEM: Neural link established. Scanning sector...", ]); @@ -47,6 +49,7 @@ const DungeonScreen = ({ session, socket }) => { socket.on("dungeon:combat_result", (data) => { if (data.message) addLog(data.message); + if (data.enemyHp !== undefined) { const maxHp = currentEnemy?.stats?.health || 100; const hpPercent = (data.enemyHp / maxHp) * 100; @@ -55,12 +58,27 @@ const DungeonScreen = ({ session, socket }) => { if (data.targetDefeated) { setIsEnemyDefeated(true); addLog("TARGET_NEUTRALIZED: Threat eliminated."); + + if (data.loot && data.loot.length > 0) { + addLog("SCANNING FOR DROPPED ASSETS..."); + data.loot.forEach((item) => { + const itemData = GameDataManager.getItem(item.id); + const itemName = itemData?.displayName || item.id; + addLog(`RECOVERED: ${itemName} x${item.count}`); + }); + } } }); + socket.on("dungeon:completed", (data) => { + setSummary(data.rewards); + addLog("MISSION_SUCCESS: All objectives secured."); + }); + return () => { socket.off("dungeon:room_update"); socket.off("dungeon:combat_result"); + socket.off("dungeon:completed"); }; }, [socket, currentEnemy]); @@ -91,6 +109,13 @@ const DungeonScreen = ({ session, socket }) => { return (
+ {summary && ( + window.location.reload()} + /> + )} +
diff --git a/client/src/views/GameInterface/tabs/DatapackTab.jsx b/client/src/views/GameInterface/tabs/DatapackTab.jsx index 7803eb9..ee0aa5f 100644 --- a/client/src/views/GameInterface/tabs/DatapackTab.jsx +++ b/client/src/views/GameInterface/tabs/DatapackTab.jsx @@ -58,62 +58,92 @@ const DatapackTab = () => { return (
-
-
- {sections.map((s) => ( - - ))} + +
+
+ {sections.map((s) => ( + + ))} +
+ +
+ + setSearchQuery(e.target.value)} + /> +
-
- - setSearchQuery(e.target.value)} - /> -
-
+
+
+ {displayList.map((item) => ( +
+ setSelectedItem({ ...item, sectionType: activeSection }) + } + > +
+ {item.texture ? ( + + ) : ( +
+ {item.displayName?.[0] || "?"} +
+ )} +
- -
- {displayList.map((item) => ( -
- setSelectedItem({ ...item, sectionType: activeSection }) - } - > -
- {item.texture ? ( - - ) : ( -
- {item.displayName?.[0] || "?"} -
- )} +
+ {item.displayName} + {item.id} + + {activeSection === "hostiles" && + item.loot && + item.loot.length > 0 && ( +
+ {item.loot.map((lootEntry, idx) => { + const lootData = GameDataManager.getItem( + lootEntry.id, + ); + return ( +
+ + + {lootData?.displayName || + lootEntry.id.split(":").pop()} + +
+ ); + })} +
+ )} +
-
- {item.displayName} - {item.id} -
-
- ))} + ))} +
- {selectedItem && ( { const [dungeons, setDungeons] = useState([]); const [selectedDungeon, setSelectedDungeon] = useState(null); + const [showSelector, setShowSelector] = useState(true); useEffect(() => { const allKeys = Array.from(GameDataManager.dungeons.keys()); - const uniqueDungeons = Array.from(new Set(allKeys)) .map((id) => GameDataManager.getDungeon(id)) .filter( @@ -16,7 +16,6 @@ const DungeonsTab = ({ startDungeon }) => { ); setDungeons(uniqueDungeons); - if (uniqueDungeons.length > 0 && !selectedDungeon) { setSelectedDungeon(uniqueDungeons[0]); } @@ -25,11 +24,16 @@ const DungeonsTab = ({ startDungeon }) => { const handleSelectDungeon = (id) => { const translatedDungeon = GameDataManager.getDungeon(id); setSelectedDungeon(translatedDungeon); + if (window.innerWidth <= 768) { + setShowSelector(false); + } }; return (
-
+

AVAILABLE_MISSIONS

@@ -57,8 +61,18 @@ const DungeonsTab = ({ startDungeon }) => { {selectedDungeon ? (
-
MISSION_BRIEFING
-

{selectedDungeon.displayName}

+ +
+
MISSION_BRIEFING
+

+ {selectedDungeon.displayName} +

+
@@ -92,10 +106,10 @@ const DungeonsTab = ({ startDungeon }) => {
- {item.displayName} + {item?.displayName || loot.itemId} - {loot.chance}% ACQUISITION_CHANCE + {loot.chance}% ACQUISITION
diff --git a/client/src/views/GameInterface/tabs/components/CraftModal.css b/client/src/views/GameInterface/tabs/components/CraftModal.css index 5c97861..88bf4a8 100644 --- a/client/src/views/GameInterface/tabs/components/CraftModal.css +++ b/client/src/views/GameInterface/tabs/components/CraftModal.css @@ -18,24 +18,23 @@ border-radius: 12px; width: 90%; max-width: 450px; - padding: 24px; box-shadow: 0 0 30px rgba(0, 204, 255, 0.15); } -.modal-header { +.modal-headerr { display: flex; justify-content: space-between; align-items: center; + padding: 10px; + text-align: center; border-bottom: 1px solid rgba(255, 255, 255, 0.1); - padding-bottom: 15px; - margin-bottom: 20px; } -.modal-header h3 { +.modal-header { margin: 0; color: #00ccff; font-family: "Orbitron", sans-serif; - font-size: 1.1rem; + font-size: 0.8rem; } .close-x { @@ -125,7 +124,7 @@ height: 100%; background: var(--primary-color); box-shadow: 0 0 10px var(--primary-color); - transition: width 1s linear; /* Плавне заповнення */ + transition: width 1s linear; } @keyframes pulse { @@ -140,7 +139,6 @@ } } -/* Стани інгредієнтів */ .res-item { display: flex; justify-content: space-between; @@ -161,7 +159,6 @@ background: rgba(255, 68, 68, 0.1); } -/* Кольори значень */ .val-red { color: #ff4444; font-weight: bold; @@ -181,7 +178,6 @@ font-size: 0.9rem; } -/* Кнопка */ .btn-start-craft { background: #28a745; color: white; @@ -202,3 +198,60 @@ background: #218838; box-shadow: 0 0 10px rgba(40, 167, 69, 0.4); } + +.item-preview-header { + display: flex; + gap: 20px; + padding: 15px; + background: rgba(0, 212, 255, 0.05); + border: 1px solid rgba(0, 212, 255, 0.1); + margin-bottom: 20px; + border-radius: 4px; +} + +.item-icon-container { + position: relative; + width: 90px; + height: 90px; + background: #000; + border: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.item-display-icon { + max-width: 80%; + max-height: 80%; + object-fit: contain; + filter: drop-shadow(0 0 5px var(--primary-color)); +} + +.item-qty-badge { + position: absolute; + top: -8px; + right: -8px; + background: var(--primary-color); + color: #000; + padding: 2px 8px; + font-size: 11px; + font-weight: bold; + border-radius: 2px; +} + +.item-type-tag { + display: block; + font-size: 10px; + color: var(--primary-color); + letter-spacing: 1px; + margin-bottom: 5px; + opacity: 0.8; +} + +.item-description { + font-size: 13px; + color: #ccc; + line-height: 1.4; + margin: 0; +} diff --git a/client/src/views/GameInterface/tabs/components/CraftModal.jsx b/client/src/views/GameInterface/tabs/components/CraftModal.jsx index be5c3a0..d9a4d94 100644 --- a/client/src/views/GameInterface/tabs/components/CraftModal.jsx +++ b/client/src/views/GameInterface/tabs/components/CraftModal.jsx @@ -1,5 +1,6 @@ import React from "react"; import "./CraftModal.css"; +import { getServerUrl } from "../../../../config/api"; const CraftModal = ({ recipe, @@ -10,6 +11,15 @@ const CraftModal = ({ }) => { if (!recipe) return null; + const CONNECT_URL = getServerUrl(); + const ASSET_BASE_URL = `${CONNECT_URL}/static/`; + + const getFullTextureUrl = (path) => { + if (!path) return "/assets/no-image.png"; + if (path.startsWith("http")) return path; + return `${ASSET_BASE_URL}${path}`; + }; + const isBusy = !!activeCraft; const outputQty = Object.values(recipe.output || {})[0] || 1; const canAfford = recipe.ingredients?.every( @@ -19,7 +29,7 @@ const CraftModal = ({ return (
e.stopPropagation()}> -
+

Construction: {recipe.displayName}

@@ -29,6 +39,25 @@ const CraftModal = ({
+ {/* Секція з картинкою предмета */} +
+
+ {recipe.displayName} +
x{outputQty}
+
+
+ PROTOTYPE_UNIT +

+ {recipe.description || + "Technical data encrypted or unavailable."} +

+
+
+

Required Resources diff --git a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css index e2b364a..3259bc6 100644 --- a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css +++ b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css @@ -10,10 +10,11 @@ animation: modalSlideUp 0.3s ease-out; } -.modal-header { +.modal-headerr { display: flex; align-items: center; gap: 20px; + justify-content: left; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); @@ -91,3 +92,61 @@ opacity: 1; } } + +.loot-list-full { + display: flex; + flex-direction: column; + gap: 10px; + background: rgba(0, 0, 0, 0.2); + padding: 12px; + border-radius: 8px; +} + +.loot-detail-item { + display: flex; + align-items: center; + gap: 12px; + padding: 8px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.loot-detail-item:last-child { + border-bottom: none; +} + +.loot-item-icon { + width: 40px; + height: 40px; + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; +} + +.loot-item-icon img { + width: 32px; + height: 32px; + object-fit: contain; +} + +.loot-item-info { + display: flex; + flex-direction: column; +} + +.loot-item-name { + font-weight: 600; + color: #fff; + font-size: 14px; +} + +.loot-item-meta { + font-size: 12px; + color: #aaa; +} + +.fallback-mini { + color: #444; + font-weight: bold; +} diff --git a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx index 879dd5a..182341e 100644 --- a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx +++ b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx @@ -22,6 +22,49 @@ const DatapackDetailsModal = ({ data, onClose }) => { ); }; + const renderLoot = () => { + if (!data.loot || data.loot.length === 0) return null; + + return ( +
+

Loot Table

+
+ {data.loot.map((entry, idx) => { + const itemInfo = GameDataManager.getItem(entry.id); + const countDisplay = + typeof entry.count === "object" + ? `${entry.count.min}-${entry.count.max}` + : entry.count; + + return ( +
+
+ {itemInfo?.texture ? ( + {itemInfo.displayName} + ) : ( +
?
+ )} +
+
+ + {itemInfo?.displayName || entry.id} + + + {Math.round(entry.chance * 100)}% chance • Amount:{" "} + {countDisplay} + +
+
+ ); + })} +
+
+ ); + }; + return (
{ × -
+
{data.texture ? ( @@ -63,6 +106,8 @@ const DatapackDetailsModal = ({ data, onClose }) => { )}
+ {data.sectionType === "hostiles" && renderLoot()} + {data.ingredients && (

Recipe Requirements

diff --git a/client/src/views/GameInterface/tabs/components/DungeonFinish.css b/client/src/views/GameInterface/tabs/components/DungeonFinish.css new file mode 100644 index 0000000..2e5855a --- /dev/null +++ b/client/src/views/GameInterface/tabs/components/DungeonFinish.css @@ -0,0 +1,187 @@ +.dungeon-summary-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(2, 5, 8, 0.95); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + backdrop-filter: blur(8px); +} + +.summary-card { + width: 490px; + background: #0a0f18; + border: 1px solid #00d4ff; + padding: 40px; + position: relative; + box-shadow: 0 0 50px rgba(0, 212, 255, 0.15); +} + +.summary-title { + color: #00d4ff; + font-family: "Orbitron", sans-serif; + letter-spacing: 4px; + margin-bottom: 5px; + font-size: 1.5rem; +} + +.summary-line { + height: 2px; + background: linear-gradient(90deg, #00d4ff, transparent); + margin-bottom: 30px; +} + +.reward-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-bottom: 30px; +} + +.stat-box { + background: rgba(26, 38, 56, 0.3); + padding: 15px; + border-left: 3px solid #00d4ff; + display: flex; + flex-direction: column; +} + +.stat-label { + font-size: 10px; + color: #4a5d75; + margin-bottom: 5px; +} + +.stat-value { + font-size: 1.2rem; + font-weight: bold; +} + +.loot-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); + gap: 15px; + margin-top: 15px; + max-height: 250px; + overflow-y: auto; + padding-right: 5px; +} + +.loot-item-slot { + width: 70px; + height: 70px; + background: #05080c; + border: 1px solid #1a2638; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.loot-img-container img { + max-width: 80%; + max-height: 80%; +} + +.loot-qty { + position: absolute; + bottom: 2px; + right: 5px; + font-size: 11px; + color: #00d4ff; + font-weight: bold; + text-shadow: 1px 1px 2px #000; +} + +.summary-btn { + margin-top: 40px; + width: 100%; + padding: 15px; + background: transparent; + border: 1px solid #00d4ff; + color: #00d4ff; + font-family: "Orbitron", sans-serif; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 2px; +} + +.summary-btn:hover { + background: rgba(0, 212, 255, 0.1); + box-shadow: inset 0 0 15px rgba(0, 212, 255, 0.3); +} + +@media screen and (max-width: 768px) { + .dungeon-summary-overlay { + padding: 15px; + } + + .summary-card { + width: 100%; + max-width: 400px; + padding: 25px 20px; + box-sizing: border-box; + } + + .summary-title { + font-size: 1.1rem; + letter-spacing: 2px; + text-align: center; + } + + .summary-line { + margin-bottom: 20px; + } + + .reward-stats { + grid-template-columns: 1fr; + gap: 10px; + margin-bottom: 20px; + } + + .stat-box { + padding: 10px; + } + + .stat-value { + font-size: 1rem; + } + + .loot-grid { + grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); + gap: 10px; + max-height: 200px; + } + + .loot-item-slot { + width: 60px; + height: 60px; + } + + .summary-btn { + margin-top: 25px; + padding: 12px; + font-size: 0.8rem; + letter-spacing: 1px; + } +} + +@media screen and (max-width: 380px) { + .summary-card { + padding: 20px 15px; + } + + .loot-grid { + grid-template-columns: repeat(4, 1fr); + } + + .loot-item-slot { + width: 55px; + height: 55px; + } +} diff --git a/client/src/views/GameInterface/tabs/components/DungeonFinish.jsx b/client/src/views/GameInterface/tabs/components/DungeonFinish.jsx new file mode 100644 index 0000000..03a6aa4 --- /dev/null +++ b/client/src/views/GameInterface/tabs/components/DungeonFinish.jsx @@ -0,0 +1,74 @@ +import React from "react"; +import GameDataManager from "../../../../services/GameDataManager.js"; +import { getServerUrl } from "../../../../config/api.js"; +import "./DungeonFinish.css"; +const DungeonFinish = ({ rewards, onExit }) => { + const CONNECT_URL = getServerUrl(); + const ASSET_BASE_URL = `${CONNECT_URL}/static/`; + + const getFullTextureUrl = (path) => { + if (!path) return "/assets/no-image.png"; + if (path.startsWith("http")) return path; + return `${ASSET_BASE_URL}${path}`; + }; + + return ( +
+
+
+
+

+ MISSION_ACCOMPLISHED +

+
+
+
+ +
+
+
+ EXPERIENCE_DATA + +{rewards.xp || 0} XP +
+
+ CREDITS_TRANSFER + + +{rewards.credits || 0} CR + +
+
+ +
+

ASSETS_RECOVERED

+
+ {rewards.items && rewards.items.length > 0 ? ( + rewards.items.map((item, idx) => { + const itemData = GameDataManager.getItem(item.id); + const textureUrl = getFullTextureUrl(itemData?.texture); + + return ( +
+
+ +
+ x{item.count} +
+
+ ); + }) + ) : ( +
NO_RESOURCES_FOUND
+ )} +
+
+
+ + +
+
+ ); +}; + +export default DungeonFinish; diff --git a/client/src/views/GameInterface/tabs/components/ItemModal.css b/client/src/views/GameInterface/tabs/components/ItemModal.css index 39290a0..26fa6c0 100644 --- a/client/src/views/GameInterface/tabs/components/ItemModal.css +++ b/client/src/views/GameInterface/tabs/components/ItemModal.css @@ -12,133 +12,184 @@ backdrop-filter: blur(4px); } -.modal-content { - background: #12151a; - border: 1px solid #00d2ff; - border-radius: 8px; - width: 100%; - max-width: 400px; - padding: 25px; +.datapack-modal-content { + background: #0f1115; + border: 1px solid rgba(0, 210, 255, 0.3); + width: 90%; + max-width: 450px; + border-radius: 12px; position: relative; - box-shadow: 0 0 30px rgba(0, 210, 255, 0.2); - color: #fff; - font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif; -} - -.modal-close { - position: absolute; - top: 10px; - right: 15px; - background: none; - border: none; - color: #888; - font-size: 28px; - cursor: pointer; - transition: color 0.2s; -} - -.modal-close:hover { + padding: 25px; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8); + animation: modalSlideUp 0.3s ease-out; color: #fff; } -.details-header { +.modal-header { display: flex; - justify-content: space-between; align-items: center; - margin-bottom: 15px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - padding-bottom: 10px; + gap: 20px; + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); } -.item-name { +.modal-icon-big { + width: 80px; + height: 80px; + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.modal-icon-big img { + width: 60px; + height: 60px; + object-fit: contain; +} + +.modal-icon-big.common { + border-color: #888; +} +.modal-icon-big.rare { + border-color: #0070dd; + box-shadow: inset 0 0 10px rgba(0, 112, 221, 0.2); +} +.modal-icon-big.epic { + border-color: #a335ee; + box-shadow: inset 0 0 10px rgba(163, 53, 238, 0.2); +} +.modal-icon-big.legendary { + border-color: #ff8000; + box-shadow: inset 0 0 10px rgba(255, 128, 0, 0.2); +} + +.modal-title-group h3 { margin: 0; - font-size: 1.2rem; + font-family: "Orbitron", sans-serif; + font-size: 1.3rem; text-transform: uppercase; - letter-spacing: 1px; } -.rarity-badge { - font-size: 0.7rem; - padding: 2px 8px; - border-radius: 4px; - text-transform: uppercase; - background: rgba(255, 255, 255, 0.1); +.modal-title-group h3.common { + color: #fff; } - -.item-name.common { - color: #ffffff; +.modal-title-group h3.rare { + color: #00d2ff; } -.item-name.uncommon { - color: #1eff00; -} -.item-name.rare { - color: #0070dd; -} -.item-name.epic { +.modal-title-group h3.epic { color: #a335ee; } -.item-name.legendary { +.modal-title-group h3.legendary { color: #ff8000; } -.item-description { +.modal-raw-id { + font-size: 0.7rem; + color: #888; + margin-top: 4px; + font-family: monospace; +} + +.details-description { font-size: 0.9rem; + line-height: 1.5; color: #aaa; - line-height: 1.4; margin-bottom: 20px; + font-style: italic; +} + +.details-section h4 { + font-size: 0.8rem; + text-transform: uppercase; + color: #00d2ff; + letter-spacing: 1px; + margin-bottom: 10px; + border-left: 3px solid #00d2ff; + padding-left: 10px; } .item-stats-container { - background: rgba(0, 0, 0, 0.3); + background: rgba(0, 0, 0, 0.2); padding: 10px; - border-radius: 4px; - margin-bottom: 25px; + border-radius: 8px; } .stat-row { display: flex; justify-content: space-between; - padding: 5px 0; - font-size: 0.85rem; + padding: 8px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.stat-row:last-child { + border-bottom: none; } .stat-label { - color: #00d2ff; + color: #888; + font-size: 0.85rem; display: flex; align-items: center; gap: 8px; } .stat-value { - color: #fff; + color: #00ff88; + font-family: monospace; font-weight: bold; } .btn-equip { width: 100%; padding: 12px; - background: none; + background: rgba(0, 210, 255, 0.05); border: 1px solid #00d2ff; color: #00d2ff; cursor: pointer; - text-transform: uppercase; - font-weight: bold; - letter-spacing: 1px; + font-family: "Orbitron", sans-serif; + font-size: 0.8rem; transition: all 0.2s; } .btn-equip:hover { background: #00d2ff; color: #000; - box-shadow: 0 0 15px rgba(0, 210, 255, 0.4); } .btn-equip.unequip { border-color: #ff4444; color: #ff4444; + background: rgba(255, 68, 68, 0.05); } .btn-equip.unequip:hover { background: #ff4444; color: #fff; } + +.modal-close { + position: absolute; + top: 15px; + right: 15px; + background: none; + border: none; + color: #444; + font-size: 24px; + cursor: pointer; +} + +@keyframes modalSlideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} diff --git a/client/src/views/GameInterface/tabs/components/ItemModal.jsx b/client/src/views/GameInterface/tabs/components/ItemModal.jsx index 4fd4a75..599cc29 100644 --- a/client/src/views/GameInterface/tabs/components/ItemModal.jsx +++ b/client/src/views/GameInterface/tabs/components/ItemModal.jsx @@ -1,5 +1,6 @@ import React from "react"; import "./ItemModal.css"; +import { getServerUrl } from "../../../../config/api"; const ItemModal = ({ item, @@ -12,23 +13,45 @@ const ItemModal = ({ }) => { if (!item) return null; + const CONNECT_URL = getServerUrl(); + const ASSET_BASE_URL = `${CONNECT_URL}/static/`; + + const getFullTextureUrl = (path) => { + if (!path) return "/assets/no-image.png"; + if (path.startsWith("http")) return path; + return `${ASSET_BASE_URL}${path}`; + }; + return (
-
e.stopPropagation()}> +
e.stopPropagation()} + > -
-
-

- {item.displayName || item.name} -

- {item.rarity} +
+
+ {item.displayName}
+
+

{item.displayName || item.name}

+
+ {item.rarity?.toUpperCase()} SYSTEM_ID: {item.id} +
+
+
-

{item.description}

+
+

{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); }