Merge branch 'main' of https://github.com/Korvarix/Galaxy-Strike-Online
This commit is contained in:
commit
0d20d630ce
@ -31,7 +31,6 @@ class GameDataManager {
|
|||||||
if (Array.isArray(data.rooms)) {
|
if (Array.isArray(data.rooms)) {
|
||||||
data.rooms.forEach((r) => this.rooms.set(r.id, r));
|
data.rooms.forEach((r) => this.rooms.set(r.id, r));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.languages) {
|
if (data.languages) {
|
||||||
this.translations = data.languages;
|
this.translations = data.languages;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -420,3 +420,439 @@
|
|||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
font-size: 0.6rem;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import GameDataManager from "../../../services/GameDataManager.js";
|
import GameDataManager from "../../../services/GameDataManager.js";
|
||||||
import "./DungeonScreen.css";
|
import "./DungeonScreen.css";
|
||||||
|
import DungeonFinish from "../tabs/components/DungeonFinish.jsx";
|
||||||
|
|
||||||
const DungeonScreen = ({ session, socket }) => {
|
const DungeonScreen = ({ session, socket }) => {
|
||||||
const [roomData, setRoomData] = useState(session.room);
|
const [roomData, setRoomData] = useState(session.room);
|
||||||
@ -9,6 +10,7 @@ const DungeonScreen = ({ session, socket }) => {
|
|||||||
const [enemyHp, setEnemyHp] = useState(null);
|
const [enemyHp, setEnemyHp] = useState(null);
|
||||||
const [isEnemyDefeated, setIsEnemyDefeated] = useState(false);
|
const [isEnemyDefeated, setIsEnemyDefeated] = useState(false);
|
||||||
const [isLooted, setIsLooted] = useState(false);
|
const [isLooted, setIsLooted] = useState(false);
|
||||||
|
const [summary, setSummary] = useState(null);
|
||||||
const [log, setLog] = useState([
|
const [log, setLog] = useState([
|
||||||
"SYSTEM: Neural link established. Scanning sector...",
|
"SYSTEM: Neural link established. Scanning sector...",
|
||||||
]);
|
]);
|
||||||
@ -47,6 +49,7 @@ const DungeonScreen = ({ session, socket }) => {
|
|||||||
|
|
||||||
socket.on("dungeon:combat_result", (data) => {
|
socket.on("dungeon:combat_result", (data) => {
|
||||||
if (data.message) addLog(data.message);
|
if (data.message) addLog(data.message);
|
||||||
|
|
||||||
if (data.enemyHp !== undefined) {
|
if (data.enemyHp !== undefined) {
|
||||||
const maxHp = currentEnemy?.stats?.health || 100;
|
const maxHp = currentEnemy?.stats?.health || 100;
|
||||||
const hpPercent = (data.enemyHp / maxHp) * 100;
|
const hpPercent = (data.enemyHp / maxHp) * 100;
|
||||||
@ -55,12 +58,27 @@ const DungeonScreen = ({ session, socket }) => {
|
|||||||
if (data.targetDefeated) {
|
if (data.targetDefeated) {
|
||||||
setIsEnemyDefeated(true);
|
setIsEnemyDefeated(true);
|
||||||
addLog("TARGET_NEUTRALIZED: Threat eliminated.");
|
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 () => {
|
return () => {
|
||||||
socket.off("dungeon:room_update");
|
socket.off("dungeon:room_update");
|
||||||
socket.off("dungeon:combat_result");
|
socket.off("dungeon:combat_result");
|
||||||
|
socket.off("dungeon:completed");
|
||||||
};
|
};
|
||||||
}, [socket, currentEnemy]);
|
}, [socket, currentEnemy]);
|
||||||
|
|
||||||
@ -91,6 +109,13 @@ const DungeonScreen = ({ session, socket }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dungeon-active-screen">
|
<div className="dungeon-active-screen">
|
||||||
|
{summary && (
|
||||||
|
<DungeonFinish
|
||||||
|
rewards={summary}
|
||||||
|
onExit={() => window.location.reload()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="dungeon-header">
|
<div className="dungeon-header">
|
||||||
<div className="room-progress">
|
<div className="room-progress">
|
||||||
<div className="progress-text">
|
<div className="progress-text">
|
||||||
|
|||||||
@ -58,62 +58,92 @@ const DatapackTab = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tab-content active datapack-tab-wrapper">
|
<div className="tab-content active datapack-tab-wrapper">
|
||||||
<div className="datapack-controls">
|
<MeteorRegion>
|
||||||
<div className="section-selector">
|
<div className="datapack-controls">
|
||||||
{sections.map((s) => (
|
<div className="section-selector">
|
||||||
<button
|
{sections.map((s) => (
|
||||||
key={s.id}
|
<button
|
||||||
className={`section-btn ${activeSection === s.id ? "active" : ""}`}
|
key={s.id}
|
||||||
onClick={() => setActiveSection(s.id)}
|
className={`section-btn ${activeSection === s.id ? "active" : ""}`}
|
||||||
>
|
onClick={() => setActiveSection(s.id)}
|
||||||
<i className={`fas ${s.icon}`}></i>
|
>
|
||||||
<span>{s.label}</span>
|
<i className={`fas ${s.icon}`}></i>
|
||||||
</button>
|
<span>{s.label}</span>
|
||||||
))}
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="search-bar">
|
||||||
|
<i className="fas fa-search"></i>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search ID or Name..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="search-bar">
|
<div className="datapack-content">
|
||||||
<i className="fas fa-search"></i>
|
<div className="datapack-grid">
|
||||||
<input
|
{displayList.map((item) => (
|
||||||
type="text"
|
<div
|
||||||
placeholder="Search ID or Name..."
|
key={item.id}
|
||||||
value={searchQuery}
|
className={`datapack-card ${activeSection === "hostiles" ? "hostile-card" : ""}`}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onClick={() =>
|
||||||
/>
|
setSelectedItem({ ...item, sectionType: activeSection })
|
||||||
</div>
|
}
|
||||||
</div>
|
>
|
||||||
|
<div className="card-icon">
|
||||||
|
{item.texture ? (
|
||||||
|
<img
|
||||||
|
src={`${config.serverUrl}/static/${item.texture}`}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="fallback-icon">
|
||||||
|
{item.displayName?.[0] || "?"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<MeteorRegion className="datapack-content">
|
<div className="card-info">
|
||||||
<div className="datapack-grid">
|
<span className="card-name">{item.displayName}</span>
|
||||||
{displayList.map((item) => (
|
<span className="card-id">{item.id}</span>
|
||||||
<div
|
|
||||||
key={item.id}
|
{activeSection === "hostiles" &&
|
||||||
className="datapack-card"
|
item.loot &&
|
||||||
onClick={() =>
|
item.loot.length > 0 && (
|
||||||
setSelectedItem({ ...item, sectionType: activeSection })
|
<div className="card-loot-preview">
|
||||||
}
|
{item.loot.map((lootEntry, idx) => {
|
||||||
>
|
const lootData = GameDataManager.getItem(
|
||||||
<div className="card-icon">
|
lootEntry.id,
|
||||||
{item.texture ? (
|
);
|
||||||
<img
|
return (
|
||||||
src={`${config.serverUrl}/static/${item.texture}`}
|
<div
|
||||||
alt=""
|
key={`${item.id}-loot-${idx}`}
|
||||||
/>
|
className="loot-mini-slot-text"
|
||||||
) : (
|
title={`${lootData?.displayName || lootEntry.id} (${(lootEntry.chance * 100).toFixed(0)}%)`}
|
||||||
<div className="fallback-icon">
|
>
|
||||||
{item.displayName?.[0] || "?"}
|
<i
|
||||||
</div>
|
className="fas fa-box-open"
|
||||||
)}
|
style={{ fontSize: "10px", marginRight: "4px" }}
|
||||||
|
></i>
|
||||||
|
<span>
|
||||||
|
{lootData?.displayName ||
|
||||||
|
lootEntry.id.split(":").pop()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-info">
|
))}
|
||||||
<span className="card-name">{item.displayName}</span>
|
</div>
|
||||||
<span className="card-id">{item.id}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</MeteorRegion>
|
</MeteorRegion>
|
||||||
|
|
||||||
{selectedItem && (
|
{selectedItem && (
|
||||||
<DatapackDetailsModal
|
<DatapackDetailsModal
|
||||||
data={selectedItem}
|
data={selectedItem}
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import "./styles/DungeonsTab.css";
|
|||||||
const DungeonsTab = ({ startDungeon }) => {
|
const DungeonsTab = ({ startDungeon }) => {
|
||||||
const [dungeons, setDungeons] = useState([]);
|
const [dungeons, setDungeons] = useState([]);
|
||||||
const [selectedDungeon, setSelectedDungeon] = useState(null);
|
const [selectedDungeon, setSelectedDungeon] = useState(null);
|
||||||
|
const [showSelector, setShowSelector] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const allKeys = Array.from(GameDataManager.dungeons.keys());
|
const allKeys = Array.from(GameDataManager.dungeons.keys());
|
||||||
|
|
||||||
const uniqueDungeons = Array.from(new Set(allKeys))
|
const uniqueDungeons = Array.from(new Set(allKeys))
|
||||||
.map((id) => GameDataManager.getDungeon(id))
|
.map((id) => GameDataManager.getDungeon(id))
|
||||||
.filter(
|
.filter(
|
||||||
@ -16,7 +16,6 @@ const DungeonsTab = ({ startDungeon }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setDungeons(uniqueDungeons);
|
setDungeons(uniqueDungeons);
|
||||||
|
|
||||||
if (uniqueDungeons.length > 0 && !selectedDungeon) {
|
if (uniqueDungeons.length > 0 && !selectedDungeon) {
|
||||||
setSelectedDungeon(uniqueDungeons[0]);
|
setSelectedDungeon(uniqueDungeons[0]);
|
||||||
}
|
}
|
||||||
@ -25,11 +24,16 @@ const DungeonsTab = ({ startDungeon }) => {
|
|||||||
const handleSelectDungeon = (id) => {
|
const handleSelectDungeon = (id) => {
|
||||||
const translatedDungeon = GameDataManager.getDungeon(id);
|
const translatedDungeon = GameDataManager.getDungeon(id);
|
||||||
setSelectedDungeon(translatedDungeon);
|
setSelectedDungeon(translatedDungeon);
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
setShowSelector(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tab-content active" id="dungeons-tab">
|
<div className="tab-content active" id="dungeons-tab">
|
||||||
<div className="dungeons-container">
|
<div
|
||||||
|
className={`dungeons-container ${!showSelector ? "view-active" : ""}`}
|
||||||
|
>
|
||||||
<div className="dungeon-selector">
|
<div className="dungeon-selector">
|
||||||
<div className="selector-header">
|
<div className="selector-header">
|
||||||
<h2 className="terminal-text">AVAILABLE_MISSIONS</h2>
|
<h2 className="terminal-text">AVAILABLE_MISSIONS</h2>
|
||||||
@ -57,8 +61,18 @@ const DungeonsTab = ({ startDungeon }) => {
|
|||||||
{selectedDungeon ? (
|
{selectedDungeon ? (
|
||||||
<div className="dungeon-details-v2">
|
<div className="dungeon-details-v2">
|
||||||
<div className="details-header-scan">
|
<div className="details-header-scan">
|
||||||
<div className="mission-type-label">MISSION_BRIEFING</div>
|
<button
|
||||||
<h3 className="mission-title">{selectedDungeon.displayName}</h3>
|
className="back-to-list"
|
||||||
|
onClick={() => setShowSelector(true)}
|
||||||
|
>
|
||||||
|
<i className="fas fa-arrow-left"></i>
|
||||||
|
</button>
|
||||||
|
<div className="mission-info-group">
|
||||||
|
<div className="mission-type-label">MISSION_BRIEFING</div>
|
||||||
|
<h3 className="mission-title">
|
||||||
|
{selectedDungeon.displayName}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
<div className="scanline-horizontal"></div>
|
<div className="scanline-horizontal"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -92,10 +106,10 @@ const DungeonsTab = ({ startDungeon }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="reward-text">
|
<div className="reward-text">
|
||||||
<span className="reward-name">
|
<span className="reward-name">
|
||||||
{item.displayName}
|
{item?.displayName || loot.itemId}
|
||||||
</span>
|
</span>
|
||||||
<span className="reward-chance">
|
<span className="reward-chance">
|
||||||
{loot.chance}% ACQUISITION_CHANCE
|
{loot.chance}% ACQUISITION
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,24 +18,23 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
padding: 24px;
|
|
||||||
box-shadow: 0 0 30px rgba(0, 204, 255, 0.15);
|
box-shadow: 0 0 30px rgba(0, 204, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-headerr {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
padding-bottom: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h3 {
|
.modal-header {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #00ccff;
|
color: #00ccff;
|
||||||
font-family: "Orbitron", sans-serif;
|
font-family: "Orbitron", sans-serif;
|
||||||
font-size: 1.1rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-x {
|
.close-x {
|
||||||
@ -125,7 +124,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
box-shadow: 0 0 10px var(--primary-color);
|
box-shadow: 0 0 10px var(--primary-color);
|
||||||
transition: width 1s linear; /* Плавне заповнення */
|
transition: width 1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
@ -140,7 +139,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стани інгредієнтів */
|
|
||||||
.res-item {
|
.res-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -161,7 +159,6 @@
|
|||||||
background: rgba(255, 68, 68, 0.1);
|
background: rgba(255, 68, 68, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кольори значень */
|
|
||||||
.val-red {
|
.val-red {
|
||||||
color: #ff4444;
|
color: #ff4444;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -181,7 +178,6 @@
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кнопка */
|
|
||||||
.btn-start-craft {
|
.btn-start-craft {
|
||||||
background: #28a745;
|
background: #28a745;
|
||||||
color: white;
|
color: white;
|
||||||
@ -202,3 +198,60 @@
|
|||||||
background: #218838;
|
background: #218838;
|
||||||
box-shadow: 0 0 10px rgba(40, 167, 69, 0.4);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import "./CraftModal.css";
|
import "./CraftModal.css";
|
||||||
|
import { getServerUrl } from "../../../../config/api";
|
||||||
|
|
||||||
const CraftModal = ({
|
const CraftModal = ({
|
||||||
recipe,
|
recipe,
|
||||||
@ -10,6 +11,15 @@ const CraftModal = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!recipe) return null;
|
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 isBusy = !!activeCraft;
|
||||||
const outputQty = Object.values(recipe.output || {})[0] || 1;
|
const outputQty = Object.values(recipe.output || {})[0] || 1;
|
||||||
const canAfford = recipe.ingredients?.every(
|
const canAfford = recipe.ingredients?.every(
|
||||||
@ -19,7 +29,7 @@ const CraftModal = ({
|
|||||||
return (
|
return (
|
||||||
<div className="craft-modal-overlay" onClick={onClose}>
|
<div className="craft-modal-overlay" onClick={onClose}>
|
||||||
<div className="craft-modal" onClick={(e) => e.stopPropagation()}>
|
<div className="craft-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="modal-header">
|
<div className="modal-headerr">
|
||||||
<h3>
|
<h3>
|
||||||
<i className="fas fa-tools"></i> Construction: {recipe.displayName}
|
<i className="fas fa-tools"></i> Construction: {recipe.displayName}
|
||||||
</h3>
|
</h3>
|
||||||
@ -29,6 +39,25 @@ const CraftModal = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
|
{/* Секція з картинкою предмета */}
|
||||||
|
<div className="item-preview-header">
|
||||||
|
<div className="item-icon-container">
|
||||||
|
<img
|
||||||
|
src={getFullTextureUrl(recipe.texture)}
|
||||||
|
alt={recipe.displayName}
|
||||||
|
className="item-display-icon"
|
||||||
|
/>
|
||||||
|
<div className="item-qty-badge">x{outputQty}</div>
|
||||||
|
</div>
|
||||||
|
<div className="item-header-info">
|
||||||
|
<span className="item-type-tag">PROTOTYPE_UNIT</span>
|
||||||
|
<p className="item-description">
|
||||||
|
{recipe.description ||
|
||||||
|
"Technical data encrypted or unavailable."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="requirements-section">
|
<div className="requirements-section">
|
||||||
<h4>
|
<h4>
|
||||||
<i className="fas fa-list-ul"></i> Required Resources
|
<i className="fas fa-list-ul"></i> Required Resources
|
||||||
|
|||||||
@ -10,10 +10,11 @@
|
|||||||
animation: modalSlideUp 0.3s ease-out;
|
animation: modalSlideUp 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-headerr {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
justify-content: left;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
@ -91,3 +92,61 @@
|
|||||||
opacity: 1;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -22,6 +22,49 @@ const DatapackDetailsModal = ({ data, onClose }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderLoot = () => {
|
||||||
|
if (!data.loot || data.loot.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="details-section">
|
||||||
|
<h4>Loot Table</h4>
|
||||||
|
<div className="loot-list-full">
|
||||||
|
{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 (
|
||||||
|
<div key={idx} className="loot-detail-item">
|
||||||
|
<div className="loot-item-icon">
|
||||||
|
{itemInfo?.texture ? (
|
||||||
|
<img
|
||||||
|
src={`${config.serverUrl}/static/${itemInfo.texture}`}
|
||||||
|
alt={itemInfo.displayName}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="fallback-mini">?</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="loot-item-info">
|
||||||
|
<span className="loot-item-name">
|
||||||
|
{itemInfo?.displayName || entry.id}
|
||||||
|
</span>
|
||||||
|
<span className="loot-item-meta">
|
||||||
|
{Math.round(entry.chance * 100)}% chance • Amount:{" "}
|
||||||
|
{countDisplay}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-overlay" onClick={onClose}>
|
<div className="modal-overlay" onClick={onClose}>
|
||||||
<div
|
<div
|
||||||
@ -32,7 +75,7 @@ const DatapackDetailsModal = ({ data, onClose }) => {
|
|||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="modal-header">
|
<div className="modal-headerr">
|
||||||
<div className="modal-icon-big">
|
<div className="modal-icon-big">
|
||||||
{data.texture ? (
|
{data.texture ? (
|
||||||
<img src={`${config.serverUrl}/static/${data.texture}`} alt="" />
|
<img src={`${config.serverUrl}/static/${data.texture}`} alt="" />
|
||||||
@ -63,6 +106,8 @@ const DatapackDetailsModal = ({ data, onClose }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{data.sectionType === "hostiles" && renderLoot()}
|
||||||
|
|
||||||
{data.ingredients && (
|
{data.ingredients && (
|
||||||
<div className="details-section">
|
<div className="details-section">
|
||||||
<h4>Recipe Requirements</h4>
|
<h4>Recipe Requirements</h4>
|
||||||
|
|||||||
187
client/src/views/GameInterface/tabs/components/DungeonFinish.css
Normal file
187
client/src/views/GameInterface/tabs/components/DungeonFinish.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 (
|
||||||
|
<div className="dungeon-summary-overlay">
|
||||||
|
<div className="summary-card">
|
||||||
|
<div className="summary-header">
|
||||||
|
<div className="glitch-wrapper">
|
||||||
|
<h2 className="summary-title" data-text="MISSION_ACCOMPLISHED">
|
||||||
|
MISSION_ACCOMPLISHED
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="summary-line"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="summary-body">
|
||||||
|
<div className="reward-stats">
|
||||||
|
<div className="stat-box">
|
||||||
|
<span className="stat-label">EXPERIENCE_DATA</span>
|
||||||
|
<span className="stat-value">+{rewards.xp || 0} XP</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-box">
|
||||||
|
<span className="stat-label">CREDITS_TRANSFER</span>
|
||||||
|
<span className="stat-value cyan-text">
|
||||||
|
+{rewards.credits || 0} CR
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="loot-section">
|
||||||
|
<h4 className="section-label">ASSETS_RECOVERED</h4>
|
||||||
|
<div className="loot-grid">
|
||||||
|
{rewards.items && rewards.items.length > 0 ? (
|
||||||
|
rewards.items.map((item, idx) => {
|
||||||
|
const itemData = GameDataManager.getItem(item.id);
|
||||||
|
const textureUrl = getFullTextureUrl(itemData?.texture);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={idx} className="loot-item-slot">
|
||||||
|
<div className="loot-img-container">
|
||||||
|
<img src={textureUrl} />
|
||||||
|
</div>
|
||||||
|
<span className="loot-qty">x{item.count}</span>
|
||||||
|
<div className="loot-name-hint"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div className="no-loot-msg">NO_RESOURCES_FOUND</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="summary-btn" onClick={onExit}>
|
||||||
|
CONFIRM & RETURN TO BASE
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DungeonFinish;
|
||||||
@ -12,133 +12,184 @@
|
|||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.datapack-modal-content {
|
||||||
background: #12151a;
|
background: #0f1115;
|
||||||
border: 1px solid #00d2ff;
|
border: 1px solid rgba(0, 210, 255, 0.3);
|
||||||
border-radius: 8px;
|
width: 90%;
|
||||||
width: 100%;
|
max-width: 450px;
|
||||||
max-width: 400px;
|
border-radius: 12px;
|
||||||
padding: 25px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: 0 0 30px rgba(0, 210, 255, 0.2);
|
padding: 25px;
|
||||||
color: #fff;
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8);
|
||||||
font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
animation: modalSlideUp 0.3s ease-out;
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-header {
|
.modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 15px;
|
gap: 20px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
margin-bottom: 20px;
|
||||||
padding-bottom: 10px;
|
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;
|
margin: 0;
|
||||||
font-size: 1.2rem;
|
font-family: "Orbitron", sans-serif;
|
||||||
|
font-size: 1.3rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rarity-badge {
|
.modal-title-group h3.common {
|
||||||
font-size: 0.7rem;
|
color: #fff;
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
}
|
||||||
|
.modal-title-group h3.rare {
|
||||||
.item-name.common {
|
color: #00d2ff;
|
||||||
color: #ffffff;
|
|
||||||
}
|
}
|
||||||
.item-name.uncommon {
|
.modal-title-group h3.epic {
|
||||||
color: #1eff00;
|
|
||||||
}
|
|
||||||
.item-name.rare {
|
|
||||||
color: #0070dd;
|
|
||||||
}
|
|
||||||
.item-name.epic {
|
|
||||||
color: #a335ee;
|
color: #a335ee;
|
||||||
}
|
}
|
||||||
.item-name.legendary {
|
.modal-title-group h3.legendary {
|
||||||
color: #ff8000;
|
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;
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
line-height: 1.4;
|
|
||||||
margin-bottom: 20px;
|
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 {
|
.item-stats-container {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-row {
|
.stat-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 5px 0;
|
padding: 8px 0;
|
||||||
font-size: 0.85rem;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
color: #00d2ff;
|
color: #888;
|
||||||
|
font-size: 0.85rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
color: #fff;
|
color: #00ff88;
|
||||||
|
font-family: monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-equip {
|
.btn-equip {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: none;
|
background: rgba(0, 210, 255, 0.05);
|
||||||
border: 1px solid #00d2ff;
|
border: 1px solid #00d2ff;
|
||||||
color: #00d2ff;
|
color: #00d2ff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-transform: uppercase;
|
font-family: "Orbitron", sans-serif;
|
||||||
font-weight: bold;
|
font-size: 0.8rem;
|
||||||
letter-spacing: 1px;
|
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-equip:hover {
|
.btn-equip:hover {
|
||||||
background: #00d2ff;
|
background: #00d2ff;
|
||||||
color: #000;
|
color: #000;
|
||||||
box-shadow: 0 0 15px rgba(0, 210, 255, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-equip.unequip {
|
.btn-equip.unequip {
|
||||||
border-color: #ff4444;
|
border-color: #ff4444;
|
||||||
color: #ff4444;
|
color: #ff4444;
|
||||||
|
background: rgba(255, 68, 68, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-equip.unequip:hover {
|
.btn-equip.unequip:hover {
|
||||||
background: #ff4444;
|
background: #ff4444;
|
||||||
color: #fff;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import "./ItemModal.css";
|
import "./ItemModal.css";
|
||||||
|
import { getServerUrl } from "../../../../config/api";
|
||||||
|
|
||||||
const ItemModal = ({
|
const ItemModal = ({
|
||||||
item,
|
item,
|
||||||
@ -12,23 +13,45 @@ const ItemModal = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!item) return null;
|
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 (
|
return (
|
||||||
<div className="modal-overlay" onClick={onClose}>
|
<div className="modal-overlay" onClick={onClose}>
|
||||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
<div
|
||||||
|
className="datapack-modal-content"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<button className="modal-close" onClick={onClose}>
|
<button className="modal-close" onClick={onClose}>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="details-view">
|
<div className="modal-header">
|
||||||
<div className="details-header">
|
<div className={`modal-icon-big ${item.rarity}`}>
|
||||||
<h4 className={`item-name ${item.rarity}`}>
|
<img src={getFullTextureUrl(item.texture)} alt={item.displayName} />
|
||||||
{item.displayName || item.name}
|
|
||||||
</h4>
|
|
||||||
<span className={`rarity-badge ${item.rarity}`}>{item.rarity}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="modal-title-group">
|
||||||
|
<h3 className={item.rarity}>{item.displayName || item.name}</h3>
|
||||||
|
<div className="modal-raw-id">
|
||||||
|
{item.rarity?.toUpperCase()} SYSTEM_ID: {item.id}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="item-description">{item.description}</p>
|
<div className="details-section">
|
||||||
|
<p className="details-description">{item.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="details-section">
|
||||||
|
<h4>
|
||||||
|
<i className="fas fa-microchip"></i> Technical Specs
|
||||||
|
</h4>
|
||||||
<div className="item-stats-container">
|
<div className="item-stats-container">
|
||||||
{item.stats &&
|
{item.stats &&
|
||||||
Object.entries(item.stats).map(([statName, value]) => (
|
Object.entries(item.stats).map(([statName, value]) => (
|
||||||
@ -43,7 +66,9 @@ const ItemModal = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-actions" style={{ marginTop: "20px" }}>
|
||||||
{isEquipped ? (
|
{isEquipped ? (
|
||||||
<button
|
<button
|
||||||
className="btn-equip unequip"
|
className="btn-equip unequip"
|
||||||
@ -52,7 +77,7 @@ const ItemModal = ({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
DISCONNECT_SYSTEM
|
TERMINATE_CONNECTION
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
item.canEquip && (
|
item.canEquip && (
|
||||||
|
|||||||
@ -194,6 +194,9 @@
|
|||||||
.message {
|
.message {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
min-height: min-content;
|
||||||
|
padding: 4px 0;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-time {
|
.msg-time {
|
||||||
@ -214,7 +217,12 @@
|
|||||||
|
|
||||||
.message.system .msg-text {
|
.message.system .msg-text {
|
||||||
color: #888;
|
color: #888;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
display: inline-block;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input-area {
|
.chat-input-area {
|
||||||
@ -411,3 +419,29 @@
|
|||||||
color: #ff3e3e;
|
color: #ff3e3e;
|
||||||
text-shadow: 0 0 8px rgba(255, 62, 62, 0.4);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* Контейнер вкладки */
|
|
||||||
#dungeons-tab {
|
#dungeons-tab {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #05080c;
|
background: #05080c;
|
||||||
@ -13,7 +12,6 @@
|
|||||||
border-top: 1px solid rgba(0, 212, 255, 0.2);
|
border-top: 1px solid rgba(0, 212, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Ліва панель --- */
|
|
||||||
.dungeon-selector {
|
.dungeon-selector {
|
||||||
border-right: 1px solid rgba(0, 212, 255, 0.1);
|
border-right: 1px solid rgba(0, 212, 255, 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -96,7 +94,6 @@
|
|||||||
font-family: "Space Mono", monospace;
|
font-family: "Space Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Права панель --- */
|
|
||||||
.dungeon-view {
|
.dungeon-view {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -119,6 +116,18 @@
|
|||||||
background: rgba(0, 212, 255, 0.03);
|
background: rgba(0, 212, 255, 0.03);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
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 {
|
.scanline-horizontal {
|
||||||
@ -174,11 +183,6 @@
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Нагороди --- */
|
|
||||||
.expected-rewards-section {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -195,7 +199,7 @@
|
|||||||
|
|
||||||
.rewards-grid {
|
.rewards-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,14 +213,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reward-icon-container {
|
.reward-icon-container {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
background: #0a0f18;
|
background: #0a0f18;
|
||||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 15px;
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-icon-container img {
|
.reward-icon-container img {
|
||||||
@ -225,29 +230,27 @@
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-icon-container i {
|
|
||||||
color: #4a5d75;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-text {
|
.reward-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-name {
|
.reward-name {
|
||||||
font-size: 0.85rem;
|
font-size: 0.75rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reward-chance {
|
.reward-chance {
|
||||||
font-size: 0.7rem;
|
font-size: 0.65rem;
|
||||||
color: #00ff88;
|
color: #00ff88;
|
||||||
font-family: "Space Mono", monospace;
|
font-family: "Space Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кольори раритетності */
|
|
||||||
.reward-entry.common {
|
.reward-entry.common {
|
||||||
border-left-color: #4a5d75;
|
border-left-color: #4a5d75;
|
||||||
}
|
}
|
||||||
@ -264,20 +267,15 @@
|
|||||||
border-left-color: #ffaa00;
|
border-left-color: #ffaa00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Кнопка --- */
|
|
||||||
.action-area {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.initiate-deployment-btn {
|
.initiate-deployment-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 18px;
|
||||||
background: #00d4ff;
|
background: #00d4ff;
|
||||||
border: none;
|
border: none;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-family: "Orbitron", sans-serif;
|
font-family: "Orbitron", sans-serif;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -288,15 +286,9 @@
|
|||||||
|
|
||||||
.initiate-deployment-btn:hover {
|
.initiate-deployment-btn:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
|
box-shadow: 0 0 20px rgba(0, 212, 255, 0.4);
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.initiate-deployment-btn i {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Placeholder --- */
|
|
||||||
.dungeon-placeholder {
|
.dungeon-placeholder {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -307,8 +299,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.radar-scanner {
|
.radar-scanner {
|
||||||
width: 100px;
|
width: 80px;
|
||||||
height: 100px;
|
height: 80px;
|
||||||
border: 2px solid rgba(0, 212, 255, 0.2);
|
border: 2px solid rgba(0, 212, 255, 0.2);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
position: relative;
|
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 {
|
.custom-scroll::-webkit-scrollbar {
|
||||||
width: 5px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
.custom-scroll::-webkit-scrollbar-track {
|
.custom-scroll::-webkit-scrollbar-track {
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.custom-scroll::-webkit-scrollbar-thumb {
|
.custom-scroll::-webkit-scrollbar-thumb {
|
||||||
background: #1a2638;
|
background: #1a2638;
|
||||||
border-radius: 10px;
|
border-radius: 4px;
|
||||||
}
|
|
||||||
.custom-scroll::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #00d4ff;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -233,3 +233,41 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
z-index: 2;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
BIN
game-server/datapacks/core/assets/audio/GSO-Button Sound.wav
Normal file
BIN
game-server/datapacks/core/assets/audio/GSO-Button Sound.wav
Normal file
Binary file not shown.
BIN
game-server/datapacks/core/assets/audio/GSO-Button Sound.webm
Normal file
BIN
game-server/datapacks/core/assets/audio/GSO-Button Sound.webm
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@ -3,12 +3,14 @@
|
|||||||
"id": "original:tutorial/tutorial_dungeon",
|
"id": "original:tutorial/tutorial_dungeon",
|
||||||
"displayName": "dungeons.original.tutorial.tutorial",
|
"displayName": "dungeons.original.tutorial.tutorial",
|
||||||
"description": "dungeons.original.tutorial.tutorial.desc",
|
"description": "dungeons.original.tutorial.tutorial.desc",
|
||||||
"meta":{
|
"meta": {
|
||||||
"energyCost": 0,
|
"energyCost": 0,
|
||||||
"repeatable": false,
|
"repeatable": false,
|
||||||
"missionArea":"space",
|
"missionArea": "space",
|
||||||
"raid": false, "_comment_1":"Future raid type picking, when you can have friends in the dugeon to help you.",
|
"raid": false,
|
||||||
"missionAllowed": [], "_comment_2":"Future ship type picking, when ship classes are started"
|
"_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": [
|
"rooms": [
|
||||||
{ "id": "original:tutorial/tutorial_enemy_room" },
|
{ "id": "original:tutorial/tutorial_enemy_room" },
|
||||||
|
|||||||
@ -8,6 +8,19 @@
|
|||||||
"damage": 4,
|
"damage": 4,
|
||||||
"critical.chance": 0.3,
|
"critical.chance": 0.3,
|
||||||
"attack.rate": 2
|
"attack.rate": 2
|
||||||
}
|
},
|
||||||
|
"loot": [
|
||||||
|
{
|
||||||
|
"id": "original:ore_coal",
|
||||||
|
"chance": 1.0,
|
||||||
|
"count": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "original:ore_copper",
|
||||||
|
"chance": 1.0,
|
||||||
|
"count": 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,19 @@
|
|||||||
"health": 30,
|
"health": 30,
|
||||||
"defense": 0.0,
|
"defense": 0.0,
|
||||||
"damage": 2,
|
"damage": 2,
|
||||||
"critical,chance": 0.0,
|
"critical.chance": 0.0,
|
||||||
"attack.rate": 1
|
"attack.rate": 1
|
||||||
}
|
},
|
||||||
|
"loot": [
|
||||||
|
{
|
||||||
|
"id": "original:alloy_steel",
|
||||||
|
"chance": 0.4,
|
||||||
|
"count": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
"rooms": {
|
"rooms": {
|
||||||
"id": "original:tutorial/tutorial_boss_room",
|
"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"],
|
"hostiles": ["original:tutorial/tutorial_boss_hostile"],
|
||||||
"gainXp": 4,
|
"gainXp": 4,
|
||||||
"credits": 200
|
"credits": 200,
|
||||||
|
"loot": [],
|
||||||
|
"meta": {
|
||||||
|
"isBossRoom": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
"rooms": {
|
"rooms": {
|
||||||
"id": "original:tutorial/tutorial_enemy_room",
|
"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"],
|
"hostiles": ["original:tutorial/tutorial_hostile"],
|
||||||
"gainXp": 3,
|
"gainXp": 3,
|
||||||
"credits": 30
|
"credits": 30,
|
||||||
|
"loot": [],
|
||||||
|
"meta": {
|
||||||
|
"isBossRoom": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,20 @@
|
|||||||
{
|
{
|
||||||
"rooms": {
|
"rooms": {
|
||||||
"id": "original:tutorial/tutorial_loot_room",
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"materials": {
|
"materials": {
|
||||||
"id": "original:circuit_advanced",
|
"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",
|
"displayName": "items.materials.original.circuits.advanced",
|
||||||
"description": "items.materials.original.circuits.advanced.desc",
|
"description": "items.materials.original.circuits.advanced.desc",
|
||||||
"meta": {
|
"meta": {
|
||||||
"storeCategory": "original:materials"
|
"storeCategory": "original:materials",
|
||||||
|
"storePrice": 50,
|
||||||
|
"storeSellValue": 10,
|
||||||
|
"storeShowWeight": 10,
|
||||||
|
"storeFeaturedDiscountPercentage": 0,
|
||||||
|
"storeFeaturedShowWeight": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"materials": {
|
"materials": {
|
||||||
"id": "original:circuit_ai_core",
|
"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",
|
"displayName": "items.materials.original.circuits.ai_core",
|
||||||
"description": "items.materials.original.circuits.ai_core.desc",
|
"description": "items.materials.original.circuits.ai_core.desc",
|
||||||
"meta": {
|
"meta": {
|
||||||
"storeCategory": "original:materials"
|
"storeCategory": "original:materials",
|
||||||
|
"storePrice": 50,
|
||||||
|
"storeSellValue": 10,
|
||||||
|
"storeShowWeight": 10,
|
||||||
|
"storeFeaturedDiscountPercentage": 0,
|
||||||
|
"storeFeaturedShowWeight": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"materials": {
|
"materials": {
|
||||||
"id": "original:circuit_basic",
|
"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",
|
"displayName": "items.materials.original.circuits.basic",
|
||||||
"description": "items.materials.original.circuits.basic.desc",
|
"description": "items.materials.original.circuits.basic.desc",
|
||||||
"meta": {
|
"meta": {
|
||||||
"storeCategory": "original:materials"
|
"storeCategory": "original:materials",
|
||||||
|
"storePrice": 50,
|
||||||
|
"storeSellValue": 10,
|
||||||
|
"storeShowWeight": 10,
|
||||||
|
"storeFeaturedDiscountPercentage": 0,
|
||||||
|
"storeFeaturedShowWeight": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"materials": {
|
"materials": {
|
||||||
"id": "original:circuit_processing_unit",
|
"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",
|
"displayName": "items.materials.original.circuits.processing_unit",
|
||||||
"description": "items.materials.original.circuits.processing_unit.desc",
|
"description": "items.materials.original.circuits.processing_unit.desc",
|
||||||
"meta": {
|
"meta": {
|
||||||
"storeCategory": "original:materials"
|
"storeCategory": "original:materials",
|
||||||
|
"storePrice": 50,
|
||||||
|
"storeSellValue": 10,
|
||||||
|
"storeShowWeight": 10,
|
||||||
|
"storeFeaturedDiscountPercentage": 0,
|
||||||
|
"storeFeaturedShowWeight": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"materials": {
|
"materials": {
|
||||||
"id": "original:circuit_quantum_processor",
|
"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",
|
"displayName": "items.materials.original.circuits.quantum_processor",
|
||||||
"description": "items.materials.original.circuits.quantum_processor.desc",
|
"description": "items.materials.original.circuits.quantum_processor.desc",
|
||||||
"meta": {
|
"meta": {
|
||||||
"storeCategory": "original:materials"
|
"storeCategory": "original:materials",
|
||||||
|
"storePrice": 50,
|
||||||
|
"storeSellValue": 10,
|
||||||
|
"storeShowWeight": 10,
|
||||||
|
"storeFeaturedDiscountPercentage": 0,
|
||||||
|
"storeFeaturedShowWeight": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ class DatapackLoader {
|
|||||||
const data = json[typeKey];
|
const data = json[typeKey];
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
const fullId = `${packName}:${data.id}`;
|
const fullId = `${data.id}`;
|
||||||
|
|
||||||
switch (typeKey) {
|
switch (typeKey) {
|
||||||
case "armour":
|
case "armour":
|
||||||
|
|||||||
@ -16,7 +16,6 @@ class DungeonManager {
|
|||||||
currentEnemyHp: undefined,
|
currentEnemyHp: undefined,
|
||||||
rewards: { xp: 0, credits: 0, items: [] },
|
rewards: { xp: 0, credits: 0, items: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
this.activeSessions.set(playerId, session);
|
this.activeSessions.set(playerId, session);
|
||||||
return this.getCurrentRoomData(playerId);
|
return this.getCurrentRoomData(playerId);
|
||||||
}
|
}
|
||||||
@ -27,55 +26,103 @@ class DungeonManager {
|
|||||||
|
|
||||||
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
|
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
|
||||||
const roomRef = dungeon.rooms[session.currentRoomIndex];
|
const roomRef = dungeon.rooms[session.currentRoomIndex];
|
||||||
const roomData = DatapackLoader.getRoom(roomRef.id);
|
const rawRoom = DatapackLoader.getRoom(roomRef.id);
|
||||||
if (!roomData) return null;
|
|
||||||
|
|
||||||
const hostiles = (roomData.hostiles || [])
|
if (!rawRoom) return null;
|
||||||
.map((hId) => {
|
|
||||||
const hostile = DatapackLoader.getEnemy(hId);
|
const hostiles = (rawRoom.hostiles || [])
|
||||||
return hostile ? { ...hostile } : null;
|
.map((hId) => DatapackLoader.getEnemy(hId))
|
||||||
})
|
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roomIndex: session.currentRoomIndex,
|
roomIndex: session.currentRoomIndex,
|
||||||
totalRooms: dungeon.rooms.length,
|
totalRooms: dungeon.rooms.length,
|
||||||
config: roomData,
|
config: rawRoom,
|
||||||
hostiles,
|
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) {
|
moveToNextRoom(playerId) {
|
||||||
const session = this.activeSessions.get(playerId);
|
const session = this.activeSessions.get(playerId);
|
||||||
if (!session || session.isFinished) return null;
|
if (!session || session.isFinished) return null;
|
||||||
|
|
||||||
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
|
const dungeon = DatapackLoader.getDungeon(session.dungeonId);
|
||||||
const roomRef = dungeon.rooms[session.currentRoomIndex];
|
const roomRef = dungeon.rooms[session.currentRoomIndex];
|
||||||
const currentRoom = DatapackLoader.getRoom(roomRef.id);
|
const rawRoom = DatapackLoader.getRoom(roomRef.id);
|
||||||
|
|
||||||
if (currentRoom) {
|
if (rawRoom) {
|
||||||
session.rewards.xp += currentRoom.gainXp || 0;
|
if (rawRoom.hostiles) {
|
||||||
session.rewards.credits += currentRoom.credits || 0;
|
rawRoom.hostiles.forEach((hId) => {
|
||||||
|
const enemy = DatapackLoader.getEnemy(hId);
|
||||||
if (currentRoom.loot && Array.isArray(currentRoom.loot)) {
|
if (enemy) {
|
||||||
currentRoom.loot.forEach((item) => {
|
session.rewards.xp += enemy.gainXp || 0;
|
||||||
session.rewards.items.push({ ...item });
|
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) {
|
if (session.currentRoomIndex < dungeon.rooms.length - 1) {
|
||||||
session.currentRoomIndex++;
|
session.currentRoomIndex++;
|
||||||
return this.getCurrentRoomData(playerId);
|
return this.getCurrentRoomData(playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.isFinished = true;
|
session.isFinished = true;
|
||||||
return {
|
return { status: "completed", rewards: session.rewards };
|
||||||
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) {
|
leaveDungeon(playerId) {
|
||||||
|
|||||||
@ -9,25 +9,16 @@ module.exports = (io, socket) => {
|
|||||||
try {
|
try {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
const dungeon = DatapackLoader.getDungeon(dungeonId);
|
const dungeon = DatapackLoader.getDungeon(dungeonId);
|
||||||
if (!dungeon) {
|
|
||||||
return socket.emit("error", { message: "Dungeon not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const player = await Player.findByPk(userId);
|
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" });
|
return socket.emit("error", { message: "Insufficient energy" });
|
||||||
}
|
|
||||||
|
|
||||||
await player.decrement("energy", { by: energyCost });
|
await player.decrement("energy", { by: energyCost });
|
||||||
|
|
||||||
const firstRoom = dungeonManager.startDungeon(userId, dungeonId);
|
const firstRoom = dungeonManager.startDungeon(userId, dungeonId);
|
||||||
if (!firstRoom) {
|
|
||||||
return socket.emit("error", {
|
|
||||||
message: "Failed to initialize dungeon",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit("dungeon:started", {
|
socket.emit("dungeon:started", {
|
||||||
dungeonId: dungeon.id,
|
dungeonId: dungeon.id,
|
||||||
@ -38,69 +29,34 @@ module.exports = (io, socket) => {
|
|||||||
remainingEnergy: player.energy - energyCost,
|
remainingEnergy: player.energy - energyCost,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Dungeon Start Error:", err);
|
|
||||||
socket.emit("error", { message: "Critical deployment failure" });
|
socket.emit("error", { message: "Critical deployment failure" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("dungeon:combat_step", async ({ enemyId }) => {
|
socket.on("dungeon:combat_step", async ({ enemyId }) => {
|
||||||
try {
|
const result = dungeonManager.processCombatStep(userId, enemyId);
|
||||||
if (!userId) return;
|
if (!result) return;
|
||||||
|
|
||||||
const session = dungeonManager.activeSessions.get(userId);
|
socket.emit("dungeon:combat_result", {
|
||||||
if (!session || session.isFinished) return;
|
...result,
|
||||||
|
message: result.targetDefeated
|
||||||
const enemyTemplate = DatapackLoader.getEnemy(enemyId);
|
? "Enemy eliminated!"
|
||||||
if (!enemyTemplate) {
|
: `Strike successful. Dealt ${result.damageDealt} damage.`,
|
||||||
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.on("dungeon:next_room", async () => {
|
socket.on("dungeon:next_room", async () => {
|
||||||
try {
|
try {
|
||||||
if (!userId) return;
|
|
||||||
|
|
||||||
const nextRoom = dungeonManager.moveToNextRoom(userId);
|
const nextRoom = dungeonManager.moveToNextRoom(userId);
|
||||||
if (!nextRoom) {
|
if (!nextRoom)
|
||||||
return socket.emit("error", {
|
return socket.emit("error", { message: "Navigation error" });
|
||||||
message: "Could not proceed to next room",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextRoom.status === "completed") {
|
if (nextRoom.status === "completed") {
|
||||||
await finalizeDungeon(socket, nextRoom.rewards);
|
await finalizeDungeon(socket, nextRoom.rewards);
|
||||||
} else {
|
} else {
|
||||||
socket.emit("dungeon:room_update", {
|
socket.emit("dungeon:room_update", nextRoom);
|
||||||
room: nextRoom.config,
|
|
||||||
hostiles: nextRoom.hostiles,
|
|
||||||
roomIndex: nextRoom.roomIndex,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Dungeon Progress Error:", err);
|
|
||||||
socket.emit("error", { message: "Navigation system error" });
|
socket.emit("error", { message: "Navigation system error" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -115,14 +71,33 @@ async function finalizeDungeon(socket, sessionRewards) {
|
|||||||
try {
|
try {
|
||||||
const player = await Player.findByPk(userId);
|
const player = await Player.findByPk(userId);
|
||||||
|
|
||||||
if (sessionRewards.credits > 0) {
|
if (sessionRewards.credits > 0)
|
||||||
await player.increment("credits", { by: sessionRewards.credits });
|
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", {
|
socket.emit("dungeon:completed", { rewards: sessionRewards });
|
||||||
rewards: sessionRewards,
|
} catch (err) {
|
||||||
message: "Mission successful. All objectives secured.",
|
socket.emit("error", { message: "Failed to save rewards" });
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
dungeonManager.leaveDungeon(userId);
|
dungeonManager.leaveDungeon(userId);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user