diff --git a/client/src/services/GameDataManager.js b/client/src/services/GameDataManager.js
index 81d07b5..a7186c7 100644
--- a/client/src/services/GameDataManager.js
+++ b/client/src/services/GameDataManager.js
@@ -6,7 +6,7 @@ class GameDataManager {
this.recipes = new Map();
this.skills = new Map();
this.dungeons = new Map();
- this.enemies = new Map();
+ this.hostiles = new Map();
this.translations = {};
this.manifest = {};
this.currentLang = localStorage.getItem("selected_lang") || "en_US";
@@ -24,7 +24,7 @@ class GameDataManager {
data.dungeons.forEach((d) => this.dungeons.set(d.id, d));
}
if (Array.isArray(data.enemies)) {
- data.enemies.forEach((e) => this.enemies.set(e.id, e));
+ data.enemies.forEach((e) => this.hostiles.set(e.id, e));
}
if (Array.isArray(data.skills)) {
data.skills.forEach((s) => this.skills.set(s.id, s));
@@ -105,11 +105,11 @@ class GameDataManager {
return clean;
}
- getEnemy(id) {
+ getHostile(id) {
const cleanId = this._cleanId(id);
- const enemy = this.enemies.get(cleanId);
+ const hostile = this.hostiles.get(cleanId);
- if (!enemy) {
+ if (!hostile) {
return {
id: cleanId,
displayName: `Unknown Entity (${cleanId})`,
@@ -119,8 +119,8 @@ class GameDataManager {
}
return {
- ...enemy,
- displayName: this.t(enemy.displayName),
+ ...hostile,
+ displayName: this.t(hostile.displayName),
};
}
diff --git a/client/src/views/GameInterface/GameInterface.jsx b/client/src/views/GameInterface/GameInterface.jsx
index a13f492..75e9e09 100644
--- a/client/src/views/GameInterface/GameInterface.jsx
+++ b/client/src/views/GameInterface/GameInterface.jsx
@@ -13,10 +13,10 @@ import QuestsTab from "./tabs/QuestsTab";
import ShopTab from "./tabs/ShopTab";
import CraftingTab from "./tabs/CraftingTab";
import ChatTab from "./tabs/ChatTab";
-import ItemListTab from "./tabs/ItemListTab.jsx";
import Notification from "./tabs/NotificationTab.jsx";
import DungeonScreen from "./components/DungeonScreen";
import { useSocket } from "../../hooks/useSocket.js";
+import DatapackTab from "./tabs/DatapackTab.jsx";
const GameInterface = ({ onExit }) => {
const [activeTab, setActiveTab] = useState("dashboard");
@@ -78,7 +78,7 @@ const GameInterface = ({ onExit }) => {
inventory: ,
shop: ,
crafting: ,
- itemlist: ,
+ datapack: ,
chat: ,
notifications: ,
};
diff --git a/client/src/views/GameInterface/components/Navigation.jsx b/client/src/views/GameInterface/components/Navigation.jsx
index 8a1863b..822edc8 100644
--- a/client/src/views/GameInterface/components/Navigation.jsx
+++ b/client/src/views/GameInterface/components/Navigation.jsx
@@ -14,7 +14,7 @@ const Navigation = ({ activeTab, onTabChange }) => {
{ id: "inventory", icon: "fa-archive" },
{ id: "shop", icon: "fa-store" },
{ id: "crafting", icon: "fa-hammer" },
- { id: "itemlist", icon: "fa-list-ul" },
+ { id: "datapack", icon: "fa-list-ul" },
{ id: "chat", icon: "fa-comments" },
{ id: "notifications", icon: "fa-bell" },
];
diff --git a/client/src/views/GameInterface/tabs/DatapackTab.jsx b/client/src/views/GameInterface/tabs/DatapackTab.jsx
new file mode 100644
index 0000000..7803eb9
--- /dev/null
+++ b/client/src/views/GameInterface/tabs/DatapackTab.jsx
@@ -0,0 +1,127 @@
+import React, { useState, useEffect } from "react";
+import GameDataManager from "../../../services/GameDataManager";
+import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx";
+import { config } from "../../../config/api";
+import DatapackDetailsModal from "./components/DatapackDetailsModal";
+import "./styles/DatapackTab.css";
+
+const DatapackTab = () => {
+ const [activeSection, setActiveSection] = useState("items");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [displayList, setDisplayList] = useState([]);
+ const [selectedItem, setSelectedItem] = useState(null);
+
+ const sections = [
+ { id: "items", label: "Items", icon: "fa-box" },
+ { id: "hostiles", label: "Hostiles", icon: "fa-biohazard" },
+ { id: "dungeons", label: "Dungeons", icon: "fa-dungeon" },
+ { id: "recipes", label: "Recipes", icon: "fa-scroll" },
+ ];
+
+ useEffect(() => {
+ let data = [];
+ switch (activeSection) {
+ case "items":
+ data = Array.from(GameDataManager.items.values()).map((i) =>
+ GameDataManager.getItem(i.id),
+ );
+ break;
+ case "hostiles":
+ data = Array.from(GameDataManager.hostiles.values()).map((h) =>
+ GameDataManager.getHostile(h.id),
+ );
+ break;
+ case "dungeons":
+ data = Array.from(GameDataManager.dungeons.values()).map((d) =>
+ GameDataManager.getDungeon(d.id),
+ );
+ break;
+ case "recipes":
+ data = Array.from(GameDataManager.recipes.values()).map((r) =>
+ GameDataManager.getRecipe(r.id),
+ );
+ break;
+ default:
+ data = [];
+ }
+
+ if (searchQuery) {
+ const query = searchQuery.toLowerCase();
+ data = data.filter(
+ (item) =>
+ item.displayName?.toLowerCase().includes(query) ||
+ item.id?.toLowerCase().includes(query),
+ );
+ }
+ setDisplayList(data);
+ }, [activeSection, searchQuery]);
+
+ return (
+
+
+
+ {sections.map((s) => (
+
+ ))}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+
+
+ {displayList.map((item) => (
+
+ setSelectedItem({ ...item, sectionType: activeSection })
+ }
+ >
+
+ {item.texture ? (
+

+ ) : (
+
+ {item.displayName?.[0] || "?"}
+
+ )}
+
+
+ {item.displayName}
+ {item.id}
+
+
+ ))}
+
+
+
+ {selectedItem && (
+
setSelectedItem(null)}
+ />
+ )}
+
+ );
+};
+
+export default DatapackTab;
diff --git a/client/src/views/GameInterface/tabs/ItemListTab.jsx b/client/src/views/GameInterface/tabs/ItemListTab.jsx
deleted file mode 100644
index 56f0c51..0000000
--- a/client/src/views/GameInterface/tabs/ItemListTab.jsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import React, { useState, useEffect } from "react";
-import GameDataManager from "../../../services/GameDataManager.js";
-import "./styles/ItemListTab.css";
-import { config } from "../../../config/api.js";
-import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx";
-
-const ItemListTab = () => {
- const [allItems, setAllItems] = useState([]);
- const [filteredItems, setFilteredItems] = useState([]);
- const [searchQuery, setSearchQuery] = useState("");
- const [selectedCategory, setSelectedCategory] = useState("all");
- const [selectedItem, setSelectedItem] = useState(null);
- const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
-
- const ASSET_BASE_URL = `${config.serverUrl}/static/`;
-
- useEffect(() => {
- const handleResize = () => setIsMobile(window.innerWidth <= 768);
- window.addEventListener("resize", handleResize);
-
- const itemsArray = Array.from(GameDataManager.items.keys()).map((id) =>
- GameDataManager.getItem(id),
- );
- setAllItems(itemsArray);
- setFilteredItems(itemsArray);
-
- return () => window.removeEventListener("resize", handleResize);
- }, []);
-
- useEffect(() => {
- let result = allItems;
- if (selectedCategory !== "all") {
- result = result.filter(
- (item) => item.meta?.category === selectedCategory,
- );
- }
- if (searchQuery) {
- const q = searchQuery.toLowerCase();
- result = result.filter(
- (item) =>
- item.displayName.toLowerCase().includes(q) ||
- item.id.toLowerCase().includes(q),
- );
- }
- setFilteredItems(result);
- }, [searchQuery, selectedCategory, allItems]);
-
- const getFullTextureUrl = (path) => {
- if (!path) return "/assets/no-image.png";
- return path.startsWith("http") ? path : `${ASSET_BASE_URL}${path}`;
- };
-
- const categories = [
- "all",
- ...new Set(allItems.map((i) => i.meta?.category).filter(Boolean)),
- ];
-
- const renderInspector = () => (
- isMobile && setSelectedItem(null)}
- >
- {selectedItem && (
-
e.stopPropagation()}>
- {isMobile && (
-
- )}
-
-
-
-
})
-
-
-
{selectedItem.id}
-
{selectedItem.displayName}
-
- {selectedItem.meta?.rarity?.toUpperCase()}
-
-
-
-
-
-
-
DATA_DESCRIPTION
-
{selectedItem.description}
-
-
- {selectedItem.stats &&
- Object.keys(selectedItem.stats).length > 0 && (
-
-
PARAMETER_READOUT
-
- {Object.entries(selectedItem.stats).map(([k, v]) => (
-
- {GameDataManager.getStatName(k)}
- +{v}
-
- ))}
-
-
- )}
-
-
- )}
-
- );
-
- return (
-
-
-
- setSearchQuery(e.target.value)}
- />
-
-
- {categories.map((cat) => (
-
- ))}
-
-
-
- {filteredItems.map((item) => (
- setSelectedItem(item)}
- >
-
-
})
-
-
-
{item.displayName}
-
{item.id}
-
-
- ))}
-
-
- {(!isMobile || (isMobile && selectedItem)) && renderInspector()}
-
- );
-};
-
-export default ItemListTab;
diff --git a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css
new file mode 100644
index 0000000..e2b364a
--- /dev/null
+++ b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css
@@ -0,0 +1,93 @@
+.datapack-modal-content {
+ background: #0f1115;
+ border: 1px solid var(--border-color);
+ width: 90%;
+ max-width: 450px;
+ border-radius: 12px;
+ position: relative;
+ padding: 25px;
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8);
+ animation: modalSlideUp 0.3s ease-out;
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.modal-icon-big {
+ width: 80px;
+ height: 80px;
+ background: rgba(0, 0, 0, 0.4);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-icon-big img {
+ width: 60px;
+}
+
+.modal-title-group h3 {
+ margin: 0;
+ font-family: "Orbitron", sans-serif;
+ color: var(--primary-color);
+ font-size: 1.3rem;
+}
+
+.modal-raw-id {
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ opacity: 0.6;
+}
+
+.details-description {
+ font-size: 0.9rem;
+ line-height: 1.5;
+ color: #ccc;
+ margin-bottom: 20px;
+ font-style: italic;
+}
+
+.details-section h4 {
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ color: var(--text-secondary);
+ letter-spacing: 1px;
+ margin-bottom: 10px;
+ border-left: 3px solid var(--primary-color);
+ padding-left: 10px;
+}
+
+.stat-row {
+ display: flex;
+ justify-content: space-between;
+ padding: 6px 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
+}
+
+.stat-label {
+ color: #888;
+ font-size: 0.85rem;
+}
+.stat-value {
+ color: #fff;
+ font-family: monospace;
+}
+
+@keyframes modalSlideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx
new file mode 100644
index 0000000..879dd5a
--- /dev/null
+++ b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx
@@ -0,0 +1,85 @@
+import React from "react";
+import GameDataManager from "../../../../services/GameDataManager";
+import { config } from "../../../../config/api";
+import "./DatapackDetailsModal.css";
+
+const DatapackDetailsModal = ({ data, onClose }) => {
+ if (!data) return null;
+
+ const renderStats = () => {
+ if (!data.stats) return null;
+ return (
+
+ {Object.entries(data.stats).map(([key, value]) => (
+
+
+ {GameDataManager.getStatName(key) || key}:
+
+ {value}
+
+ ))}
+
+ );
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
+ {data.texture ? (
+

+ ) : (
+
{data.displayName?.[0]}
+ )}
+
+
+
{data.displayName}
+ {data.id}
+
+
+
+
+ {data.description && (
+
{data.description}
+ )}
+
+
+
Properties & Stats
+ {renderStats()}
+
+ {data.sectionType === "hostiles" && data.level && (
+
+ Base Level:
+ {data.level}
+
+ )}
+
+
+ {data.ingredients && (
+
+
Recipe Requirements
+
+ {data.ingredients.map((ing, idx) => (
+
+ {ing.displayName}
+ x{ing.quantity}
+
+ ))}
+
+
+ )}
+
+
+
+ );
+};
+
+export default DatapackDetailsModal;
diff --git a/client/src/views/GameInterface/tabs/styles/DatapackTab.css b/client/src/views/GameInterface/tabs/styles/DatapackTab.css
new file mode 100644
index 0000000..dc12b54
--- /dev/null
+++ b/client/src/views/GameInterface/tabs/styles/DatapackTab.css
@@ -0,0 +1,153 @@
+.datapack-tab-wrapper {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.datapack-controls {
+ padding: 15px 20px;
+ background: rgba(0, 0, 0, 0.2);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.section-selector {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 15px;
+ overflow-x: auto;
+ scrollbar-width: none;
+}
+
+.section-btn {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+ padding: 10px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: all 0.2s;
+ min-width: 80px;
+}
+
+.section-btn i {
+ font-size: 1.1rem;
+}
+.section-btn span {
+ font-size: 0.75rem;
+ font-family: "Orbitron", sans-serif;
+}
+
+.section-btn.active {
+ background: rgba(var(--primary-rgb), 0.1);
+ border-color: var(--primary-color);
+ color: var(--primary-color);
+}
+
+.search-bar {
+ position: relative;
+ width: 100%;
+}
+
+.search-bar i {
+ position: absolute;
+ left: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--text-secondary);
+}
+
+.search-bar input {
+ width: 100%;
+ padding: 10px 10px 10px 35px;
+ background: #0a0a0c;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ color: #fff;
+ font-size: 0.9rem;
+}
+
+.datapack-content {
+ flex: 1;
+ padding: 20px;
+}
+
+.datapack-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 12px;
+ padding-bottom: 30px;
+}
+
+.datapack-card {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 12px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.card-icon {
+ width: 48px;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 4px;
+}
+
+.card-icon img {
+ max-width: 32px;
+}
+
+.card-info {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.card-name {
+ color: #fff;
+ font-size: 0.9rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.card-id {
+ font-size: 0.7rem;
+ color: var(--text-secondary);
+ font-family: monospace;
+}
+
+.card-meta {
+ font-size: 0.7rem;
+ color: var(--primary-color);
+ margin-top: 4px;
+}
+
+@media (max-width: 600px) {
+ .datapack-grid {
+ grid-template-columns: 1fr;
+ gap: 8px;
+ }
+
+ .datapack-controls {
+ padding: 10px;
+ }
+
+ .section-btn {
+ padding: 8px 5px;
+ }
+
+ .section-btn span {
+ font-size: 0.65rem;
+ }
+}
diff --git a/client/src/views/GameInterface/tabs/styles/ItemListTab.css b/client/src/views/GameInterface/tabs/styles/ItemListTab.css
deleted file mode 100644
index 4858264..0000000
--- a/client/src/views/GameInterface/tabs/styles/ItemListTab.css
+++ /dev/null
@@ -1,187 +0,0 @@
-.item-list-container {
- display: flex;
- height: calc(100vh - 120px);
- background: rgba(10, 15, 25, 0.95);
- color: #e0e6ed;
- font-family: "Rajdhani", "Segoe UI", sans-serif;
- border-top: 1px solid rgba(0, 255, 255, 0.1);
- overflow: hidden;
- position: relative;
-}
-
-.item-list-sidebar {
- width: 380px;
- border-right: 1px solid rgba(0, 255, 255, 0.1);
- display: flex;
- flex-direction: column;
- background: rgba(5, 10, 20, 0.5);
-}
-
-.search-box {
- padding: 20px;
-}
-
-.search-box input {
- width: 100%;
- background: rgba(0, 0, 0, 0.4);
- border: 1px solid rgba(0, 255, 255, 0.2);
- padding: 12px 15px;
- color: #00ffff;
- text-transform: uppercase;
- letter-spacing: 1px;
- font-size: 14px;
-}
-
-.category-filters {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- padding: 0 20px 15px;
-}
-
-.filter-btn {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- color: #aaa;
- padding: 4px 10px;
- font-size: 11px;
- cursor: pointer;
-}
-
-.filter-btn.active {
- background: rgba(0, 255, 255, 0.1);
- color: #00ffff;
- border-color: #00ffff;
-}
-
-.items-list-scroll {
- flex: 1;
- overflow-y: auto;
- padding: 10px 20px;
-}
-
-.item-row {
- display: flex;
- align-items: center;
- padding: 10px;
- margin-bottom: 8px;
- background: rgba(255, 255, 255, 0.02);
- border-left: 3px solid #9d9d9d;
- cursor: pointer;
-}
-
-.item-row.selected {
- background: rgba(0, 255, 255, 0.08);
-}
-
-.item-row-icon {
- width: 44px;
- height: 44px;
- background: rgba(0, 0, 0, 0.5);
- margin-right: 15px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.item-row-icon img {
- width: 32px;
- height: 32px;
- image-rendering: pixelated;
-}
-
-/* Inspector Base */
-.item-inspector {
- flex: 1;
- padding: 40px;
- overflow-y: auto;
-}
-
-.inspector-header {
- display: flex;
- gap: 25px;
- margin-bottom: 30px;
-}
-
-.header-visual {
- width: 100px;
- height: 100px;
- background: rgba(0, 0, 0, 0.4);
- border: 1px solid rgba(0, 255, 255, 0.2);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.header-visual img {
- width: 64px;
- height: 64px;
-}
-
-/* Mobile Modal Logic */
-@media (max-width: 768px) {
- .item-list-sidebar {
- width: 100%;
- border: none;
- }
-
- .item-inspector.mobile-modal {
- display: flex;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 3000;
- background: rgba(0, 0, 0, 0.85);
- backdrop-filter: blur(6px);
- align-items: center;
- justify-content: center;
- padding: 20px;
- }
-
- .inspector-content {
- background: #0d121d;
- border: 1px solid #00ffff44;
- width: 100%;
- max-height: 120vh;
- overflow-y: auto;
- position: relative;
- padding: 20px;
- box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
- }
-
- .close-inspector {
- position: absolute;
- top: 10px;
- right: 15px;
- background: none;
- border: none;
- color: #00ffff;
- font-size: 30px;
- cursor: pointer;
- }
-
- .item-inspector:not(.mobile-modal) {
- display: none;
- }
-}
-
-/* Rarity */
-.item-row.legendary {
- border-left-color: #ff8000;
-}
-.item-row.epic {
- border-left-color: #a335ee;
-}
-.item-row.rare {
- border-left-color: #0070dd;
-}
-
-@media screen and (max-width: 600px) {
- .section-title {
- font-size: 16px;
- padding: 0;
- margin: 10px;
- }
-}