Fixed inventory equipment, crafting tab scroll
Some checks are pending
Deploy MMO Server / deploy (push) Waiting to run

This commit is contained in:
MaksSlyzar 2026-05-05 01:37:32 +03:00
parent 2a266becea
commit 75897440c9
5 changed files with 170 additions and 150 deletions

View File

@ -36,7 +36,7 @@ class GameDataManager {
data.quests.forEach((q) => this.quests.set(q.id, q)); data.quests.forEach((q) => this.quests.set(q.id, q));
} }
console.log(this.skills); console.log(this.recipes);
if (data.languages) { if (data.languages) {
this.translations = data.languages; this.translations = data.languages;
} }

View File

@ -50,7 +50,7 @@ const Navigation = ({ activeTab, onTabChange }) => {
if (id === "itemlist") return "ITEM_LIST"; if (id === "itemlist") return "ITEM_LIST";
if (id === "chat") return "CHAT"; if (id === "chat") return "CHAT";
if (id === "notifications") return "ALERTS"; if (id === "notifications") return "ALERTS";
return GameDataManager.t(`category.tabs.original.${id}`); return GameDataManager.t(`category.tabs.core.${id}`);
}; };
return ( return (

View File

@ -2,14 +2,12 @@ import React, { useState, useEffect } from "react";
import { useSocket } from "../../../hooks/useSocket"; import { useSocket } from "../../../hooks/useSocket";
import GameDataManager from "../../../services/GameDataManager"; import GameDataManager from "../../../services/GameDataManager";
import "./styles/CraftingTab.css"; import "./styles/CraftingTab.css";
import CategorySelector from "../components/CategorySelector";
import CraftModal from "./components/CraftModal"; import CraftModal from "./components/CraftModal";
import { config } from "../../../config/api"; import { config } from "../../../config/api";
import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx"; import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx";
const CraftingTab = () => { const CraftingTab = () => {
const { socket } = useSocket(); const { socket } = useSocket();
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const [recipes, setRecipes] = useState([]); const [recipes, setRecipes] = useState([]);
const [userInventory, setUserInventory] = useState([]); const [userInventory, setUserInventory] = useState([]);
@ -27,9 +25,7 @@ const CraftingTab = () => {
useEffect(() => { useEffect(() => {
if (activeCategory) { if (activeCategory) {
const filteredRecipes = setRecipes(GameDataManager.getRecipesByCategory(activeCategory));
GameDataManager.getRecipesByCategory(activeCategory);
setRecipes(filteredRecipes);
} }
}, [activeCategory]); }, [activeCategory]);
@ -43,8 +39,7 @@ const CraftingTab = () => {
const handleCraftStarted = (data) => { const handleCraftStarted = (data) => {
const recipeData = GameDataManager.getRecipe(data.recipeId); const recipeData = GameDataManager.getRecipe(data.recipeId);
const now = Date.now(); const diff = (data.finishAt - Date.now()) / 1000;
const diff = (data.finishAt - now) / 1000;
if (diff <= 0) { if (diff <= 0) {
setActiveCraft(null); setActiveCraft(null);
@ -59,9 +54,7 @@ const CraftingTab = () => {
timeLeft: Math.max(0, Math.ceil(diff)), timeLeft: Math.max(0, Math.ceil(diff)),
}); });
if (recipeData) { if (recipeData) setSelectedRecipe(recipeData);
setSelectedRecipe(recipeData);
}
}; };
const handleCraftSuccess = () => { const handleCraftSuccess = () => {
@ -83,11 +76,11 @@ const CraftingTab = () => {
useEffect(() => { useEffect(() => {
if (!activeCraft) return; if (!activeCraft) return;
const timer = setInterval(() => { const timer = setInterval(() => {
const now = Date.now(); const diff = Math.max(
const diff = Math.max(0, Math.ceil((activeCraft.finishAt - now) / 1000)); 0,
Math.ceil((activeCraft.finishAt - Date.now()) / 1000),
);
if (diff <= 0) { if (diff <= 0) {
clearInterval(timer); clearInterval(timer);
setActiveCraft(null); setActiveCraft(null);
@ -95,7 +88,6 @@ const CraftingTab = () => {
setActiveCraft((prev) => (prev ? { ...prev, timeLeft: diff } : null)); setActiveCraft((prev) => (prev ? { ...prev, timeLeft: diff } : null));
} }
}, 1000); }, 1000);
return () => clearInterval(timer); return () => clearInterval(timer);
}, [activeCraft?.finishAt]); }, [activeCraft?.finishAt]);
@ -119,8 +111,7 @@ const CraftingTab = () => {
<div className="active-craft-panel"> <div className="active-craft-panel">
<div className="craft-info"> <div className="craft-info">
<span> <span>
<i className="fas fa-hammer fa-spin"></i> Assembling:{" "} <i className="fas fa-hammer fa-spin"></i> {activeCraft.name}
{activeCraft.name}
</span> </span>
<span className="time-left">{activeCraft.timeLeft}s</span> <span className="time-left">{activeCraft.timeLeft}s</span>
</div> </div>
@ -137,27 +128,32 @@ const CraftingTab = () => {
<div className="crafting-header"> <div className="crafting-header">
<h2> <h2>
<i className="fas fa-microchip"></i> Fabrication Unit <i className="fas fa-microchip"></i> Fabrication
</h2> </h2>
</div> </div>
<CategorySelector <div className="crafting-categories">
categories={categories} {categories.map((cat) => (
activeCategory={activeCategory} <button
onCategoryChange={setActiveCategory} key={cat.id}
/> className={`crafting-cat-btn ${activeCategory === cat.id ? "active" : ""}`}
onClick={() => setActiveCategory(cat.id)}
>
{cat.displayName || cat.id}
</button>
))}
</div>
<div className="crafting-grid"> <div className="crafting-grid">
{recipes.map((recipe) => { {recipes.map((recipe) => {
const isThisRecipeCrafting = activeCraft?.recipeId === recipe.id; const isCrafting = activeCraft?.recipeId === recipe.id;
const canCraft = recipe.ingredients.every( const canCraft = recipe.ingredients.every(
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity, (ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
); );
return ( return (
<div <div
key={recipe.id} key={recipe.id}
className={`recipe-card ${!canCraft && !isThisRecipeCrafting ? "insufficient-resources" : ""} ${isThisRecipeCrafting ? "crafting-active" : ""}`} className={`recipe-card ${!canCraft && !isCrafting ? "insufficient-resources" : ""} ${isCrafting ? "crafting-active" : ""}`}
onClick={() => setSelectedRecipe(recipe)} onClick={() => setSelectedRecipe(recipe)}
> >
<div className="recipe-icon"> <div className="recipe-icon">
@ -165,21 +161,17 @@ const CraftingTab = () => {
<img <img
width={64} width={64}
src={`${config.serverUrl}/static/${recipe.texture}`} src={`${config.serverUrl}/static/${recipe.texture}`}
alt={recipe.displayName} alt=""
/> />
) : ( ) : (
<div className="fallback-icon">{recipe.displayName[0]}</div> <div className="fallback-icon">{recipe.displayName[0]}</div>
)} )}
</div> </div>
<div className="recipe-info-main"> <span className="recipe-name">{recipe.displayName}</span>
<span className="recipe-name">{recipe.displayName}</span> <span className="badge-time">
<div className="recipe-badges"> <i className="fas fa-clock"></i> {recipe.time_seconds}s
<span className="badge-time"> </span>
<i className="fas fa-clock"></i> {recipe.time_seconds}s {isCrafting && (
</span>
</div>
</div>
{isThisRecipeCrafting && (
<div className="craft-overlay-mini"> <div className="craft-overlay-mini">
<i className="fas fa-sync fa-spin"></i> <i className="fas fa-sync fa-spin"></i>
</div> </div>

View File

@ -19,7 +19,6 @@ const InventoryTab = () => {
const manifest = GameDataManager.manifest || {}; const manifest = GameDataManager.manifest || {};
const coreSystems = manifest.core_systems?.categories || {}; const coreSystems = manifest.core_systems?.categories || {};
const getFullTextureUrl = (path) => { const getFullTextureUrl = (path) => {
if (!path) return "/assets/no-image.png"; if (!path) return "/assets/no-image.png";
if (path.startsWith("http")) return path; if (path.startsWith("http")) return path;
@ -89,36 +88,43 @@ const InventoryTab = () => {
const equipmentSlots = { const equipmentSlots = {
personal: Object.keys(coreSystems) personal: Object.keys(coreSystems)
.filter((k) => {
const system = coreSystems[k];
return (
system.slotType === "personal" &&
!k.includes("accessory") &&
k !== "original:weapon"
);
})
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
weapons: Object.keys(coreSystems)
.filter((k) => k === "original:weapon")
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
accessories: Object.keys(coreSystems)
.filter( .filter(
(k) => (k) =>
k.startsWith("original:personal_") && k.includes("accessory") && coreSystems[k].slotType === "personal",
!k.includes("accessory") &&
k !== "original:personal_weapons",
) )
.map((k) => ({ .map((k) => ({
id: k, id: k,
label: GameDataManager.t(coreSystems[k].displayName), label: GameDataManager.t(coreSystems[k].displayName),
})), })),
weapons: Object.keys(coreSystems)
.filter((k) => k === "original:personal_weapons")
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
accessories: Object.keys(coreSystems)
.filter((k) => k.includes("personal_accessory"))
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
ship: Object.keys(coreSystems) ship: Object.keys(coreSystems)
.filter((k) => k.startsWith("original:ship_")) .filter((k) => coreSystems[k].slotType === "ship")
.map((k) => ({ .map((k) => ({
id: k, id: k,
label: GameDataManager.t(coreSystems[k].displayName), label: GameDataManager.t(coreSystems[k].displayName),
})), })),
}; };
const renderSlotGroup = (title, groupSlots) => ( const renderSlotGroup = (title, groupSlots) => (
<div className="equip-group"> <div className="equip-group">
<div className="group-label">{title}</div> <div className="group-label">{title}</div>

View File

@ -3,83 +3,56 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
} }
.crafting-header { .crafting-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 15px;
} }
.crafting-header h2 { .crafting-header h2 {
font-size: 1.2rem; font-size: 1.2rem;
} margin: 0;
color: var(--primary-color);
.crafting-grid { text-transform: uppercase;
display: grid; letter-spacing: 1px;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 15px;
margin-top: 20px;
}
.recipe-card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
padding: 15px;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.recipe-card:hover {
transform: translateY(-5px);
border-color: var(--primary-color);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.recipe-icon img {
max-width: 50px;
height: auto;
}
.recipe-name {
font-size: 0.85rem;
line-height: 1.2;
color: #fff;
font-weight: 500;
}
.badge-time {
font-size: 0.75rem;
color: var(--text-secondary);
} }
.crafting-categories { .crafting-categories {
display: flex; display: flex;
flex-wrap: nowrap;
gap: 10px; gap: 10px;
margin-bottom: 15px; margin-bottom: 20px;
padding: 5px 0; padding: 5px 5px 12px 5px;
overflow-x: auto; overflow-x: auto;
scrollbar-width: none;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: var(--primary-color) transparent;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
} }
.crafting-categories::-webkit-scrollbar { .crafting-categories::-webkit-scrollbar {
display: none; height: 3px;
}
.crafting-categories::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
}
.crafting-categories::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 10px;
} }
.crafting-cat-btn { .crafting-cat-btn {
flex: 0 0 auto; flex: 0 0 auto;
padding: 8px 16px; padding: 10px 18px;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 4px; border-left: 3px solid var(--border-color);
color: var(--text-secondary); color: var(--text-secondary);
font-family: "Orbitron", sans-serif; font-family: "Orbitron", sans-serif;
font-size: 0.75rem; font-size: 0.75rem;
@ -90,56 +63,105 @@
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.crafting-cat-btn.active { .crafting-cat-btn:hover {
background: var(--primary-color); background: rgba(255, 255, 255, 0.08);
border-color: var(--primary-color); border-color: var(--primary-color);
color: #000; color: #fff;
}
.crafting-cat-btn.active {
background: linear-gradient(
90deg,
rgba(0, 243, 255, 0.15) 0%,
transparent 100%
);
border-color: var(--primary-color);
border-left: 3px solid var(--primary-color);
color: var(--primary-color);
font-weight: 700; font-weight: 700;
box-shadow: inset 5px 0 10px rgba(0, 243, 255, 0.05);
} }
@media (max-width: 600px) { .crafting-grid {
.crafting-container { display: grid;
padding: 12px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
} gap: 15px;
overflow-y: auto;
.crafting-header { padding-right: 5px;
margin-bottom: 12px;
}
.crafting-header h2 {
font-size: 1rem;
}
.crafting-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-top: 10px;
}
.recipe-card {
padding: 10px;
gap: 6px;
}
.recipe-icon img {
max-width: 40px;
}
.recipe-name {
font-size: 0.75rem;
}
.crafting-cat-btn {
padding: 6px 12px;
font-size: 0.7rem;
}
} }
.tab-content.active { .crafting-grid::-webkit-scrollbar {
height: 100%; width: 4px;
}
.crafting-grid::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.recipe-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
padding: 15px;
border-radius: 4px;
text-align: center;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
position: relative;
}
.recipe-card:hover {
background: rgba(255, 255, 255, 0.06);
border-color: var(--primary-color);
transform: translateY(-2px);
}
.recipe-card.insufficient-resources {
opacity: 0.6;
filter: grayscale(0.5);
}
.recipe-card.crafting-active {
border-color: var(--primary-color);
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
}
.active-craft-panel {
background: rgba(0, 243, 255, 0.05);
border: 1px solid var(--primary-color);
padding: 12px;
margin-bottom: 20px;
border-radius: 4px;
}
.craft-info {
display: flex;
justify-content: space-between;
font-family: "Orbitron", sans-serif;
font-size: 0.8rem;
margin-bottom: 8px;
}
.progress-bar-bg {
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden; overflow: hidden;
} }
.meteor-region-content { .progress-bar-fill {
width: 100%; height: 100%;
background: var(--primary-color);
box-shadow: 0 0 10px var(--primary-color);
transition: width 1s linear;
}
@media (max-width: 600px) {
.crafting-grid {
grid-template-columns: repeat(2, 1fr);
}
} }