crafting system
This commit is contained in:
parent
aefa586335
commit
f41eeb0e08
@ -11,7 +11,6 @@ const CraftingTab = () => {
|
|||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [recipes, setRecipes] = useState([]);
|
const [recipes, setRecipes] = useState([]);
|
||||||
const [userInventory, setUserInventory] = useState([]);
|
const [userInventory, setUserInventory] = useState([]);
|
||||||
|
|
||||||
const [activeCategory, setActiveCategory] = useState("");
|
const [activeCategory, setActiveCategory] = useState("");
|
||||||
const [selectedRecipe, setSelectedRecipe] = useState(null);
|
const [selectedRecipe, setSelectedRecipe] = useState(null);
|
||||||
const [activeCraft, setActiveCraft] = useState(null);
|
const [activeCraft, setActiveCraft] = useState(null);
|
||||||
@ -19,7 +18,6 @@ const CraftingTab = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const manifestCategories = GameDataManager.getRecipeCategories();
|
const manifestCategories = GameDataManager.getRecipeCategories();
|
||||||
setCategories(manifestCategories);
|
setCategories(manifestCategories);
|
||||||
|
|
||||||
if (manifestCategories.length > 0 && !activeCategory) {
|
if (manifestCategories.length > 0 && !activeCategory) {
|
||||||
setActiveCategory(manifestCategories[0].id);
|
setActiveCategory(manifestCategories[0].id);
|
||||||
}
|
}
|
||||||
@ -29,7 +27,6 @@ const CraftingTab = () => {
|
|||||||
if (activeCategory) {
|
if (activeCategory) {
|
||||||
const filteredRecipes =
|
const filteredRecipes =
|
||||||
GameDataManager.getRecipesByCategory(activeCategory);
|
GameDataManager.getRecipesByCategory(activeCategory);
|
||||||
console.log(filteredRecipes);
|
|
||||||
setRecipes(filteredRecipes);
|
setRecipes(filteredRecipes);
|
||||||
}
|
}
|
||||||
}, [activeCategory]);
|
}, [activeCategory]);
|
||||||
@ -38,30 +35,67 @@ const CraftingTab = () => {
|
|||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
|
||||||
socket.emit("player:get_inventory");
|
socket.emit("player:get_inventory");
|
||||||
|
socket.emit("player:check_active_craft");
|
||||||
|
|
||||||
const handleInventory = (data) => setUserInventory(data);
|
const handleInventory = (data) => setUserInventory(data);
|
||||||
|
|
||||||
|
const handleCraftStarted = (data) => {
|
||||||
|
const recipeData = GameDataManager.getRecipe(data.recipeId);
|
||||||
|
const now = Date.now();
|
||||||
|
const diff = (data.finishAt - now) / 1000;
|
||||||
|
|
||||||
|
if (diff <= 0) {
|
||||||
|
setActiveCraft(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveCraft({
|
||||||
|
recipeId: data.recipeId,
|
||||||
|
name: recipeData?.displayName || data.recipeId,
|
||||||
|
finishAt: data.finishAt,
|
||||||
|
totalTime: data.totalTime || recipeData?.time_seconds || diff,
|
||||||
|
timeLeft: Math.max(0, Math.ceil(diff)),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recipeData) {
|
||||||
|
setSelectedRecipe(recipeData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCraftSuccess = () => {
|
||||||
|
setActiveCraft(null);
|
||||||
|
setSelectedRecipe(null);
|
||||||
|
socket.emit("player:get_inventory");
|
||||||
|
};
|
||||||
|
|
||||||
socket.on("player:inventory_data", handleInventory);
|
socket.on("player:inventory_data", handleInventory);
|
||||||
|
socket.on("player:craft_started", handleCraftStarted);
|
||||||
|
socket.on("player:craft_success", handleCraftSuccess);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("player:inventory_data", handleInventory);
|
socket.off("player:inventory_data", handleInventory);
|
||||||
|
socket.off("player:craft_started", handleCraftStarted);
|
||||||
|
socket.off("player:craft_success", handleCraftSuccess);
|
||||||
};
|
};
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer;
|
if (!activeCraft) return;
|
||||||
if (activeCraft && activeCraft.timeLeft > 0) {
|
|
||||||
timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
setActiveCraft((prev) => ({
|
const now = Date.now();
|
||||||
...prev,
|
const diff = Math.max(0, Math.ceil((activeCraft.finishAt - now) / 1000));
|
||||||
timeLeft: Math.max(0, prev.timeLeft - 1),
|
|
||||||
}));
|
if (diff <= 0) {
|
||||||
}, 1000);
|
clearInterval(timer);
|
||||||
} else if (activeCraft && activeCraft.timeLeft === 0) {
|
|
||||||
setActiveCraft(null);
|
setActiveCraft(null);
|
||||||
socket.emit("player:get_inventory");
|
} else {
|
||||||
|
setActiveCraft((prev) => (prev ? { ...prev, timeLeft: diff } : null));
|
||||||
}
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [activeCraft, socket]);
|
}, [activeCraft?.finishAt]);
|
||||||
|
|
||||||
const getOwnedAmount = (itemId) => {
|
const getOwnedAmount = (itemId) => {
|
||||||
const item = userInventory.find((i) => (i.itemId || i.id) === itemId);
|
const item = userInventory.find((i) => (i.itemId || i.id) === itemId);
|
||||||
@ -70,19 +104,7 @@ const CraftingTab = () => {
|
|||||||
|
|
||||||
const handleStartCrafting = (recipe) => {
|
const handleStartCrafting = (recipe) => {
|
||||||
if (activeCraft) return;
|
if (activeCraft) return;
|
||||||
|
socket.emit("player:craft_item", { recipeId: recipe.id });
|
||||||
socket.emit("player:craft_item", {
|
|
||||||
recipeId: recipe.id,
|
|
||||||
category: activeCategory,
|
|
||||||
});
|
|
||||||
|
|
||||||
setActiveCraft({
|
|
||||||
name: recipe.displayName,
|
|
||||||
timeLeft: recipe.constructionTime,
|
|
||||||
totalTime: recipe.constructionTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
setSelectedRecipe(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -101,7 +123,7 @@ const CraftingTab = () => {
|
|||||||
<div
|
<div
|
||||||
className="progress-bar-fill"
|
className="progress-bar-fill"
|
||||||
style={{
|
style={{
|
||||||
width: `${((activeCraft.totalTime - activeCraft.timeLeft) / activeCraft.totalTime) * 100}%`,
|
width: `${Math.min(100, ((activeCraft.totalTime - activeCraft.timeLeft) / activeCraft.totalTime) * 100)}%`,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,6 +144,7 @@ const CraftingTab = () => {
|
|||||||
|
|
||||||
<div className="crafting-grid">
|
<div className="crafting-grid">
|
||||||
{recipes.map((recipe) => {
|
{recipes.map((recipe) => {
|
||||||
|
const isThisRecipeCrafting = 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,
|
||||||
);
|
);
|
||||||
@ -129,51 +152,40 @@ const CraftingTab = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={recipe.id}
|
key={recipe.id}
|
||||||
className={`recipe-card ${!canCraft ? "insufficient-resources" : ""}`}
|
className={`recipe-card ${!canCraft && !isThisRecipeCrafting ? "insufficient-resources" : ""} ${isThisRecipeCrafting ? "crafting-active" : ""}`}
|
||||||
onClick={() => setSelectedRecipe(recipe)}
|
onClick={() => setSelectedRecipe(recipe)}
|
||||||
>
|
>
|
||||||
<div className="recipe-icon">
|
<div className="recipe-icon">
|
||||||
{recipe.texture ? (
|
{recipe.texture ? (
|
||||||
<img src={recipe.texture} alt={recipe.displayName} />
|
<img src={recipe.texture} alt={recipe.displayName} />
|
||||||
) : (
|
) : (
|
||||||
<div className="fallback-icon">
|
<div className="fallback-icon">{recipe.displayName[0]}</div>
|
||||||
{recipe.id[0].toUpperCase()}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="recipe-info-main">
|
<div className="recipe-info-main">
|
||||||
<span className="recipe-name">{recipe.displayName}</span>
|
<span className="recipe-name">{recipe.displayName}</span>
|
||||||
<div className="recipe-badges">
|
<div className="recipe-badges">
|
||||||
<span className="badge-time">
|
<span className="badge-time">
|
||||||
<i className="fas fa-clock"></i> {recipe.constructionTime}
|
<i className="fas fa-clock"></i> {recipe.time_seconds}s
|
||||||
s
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isThisRecipeCrafting && (
|
||||||
{!canCraft && (
|
<div className="craft-overlay-mini">
|
||||||
<div className="lock-overlay">
|
<i className="fas fa-sync fa-spin"></i>
|
||||||
<i className="fas fa-lock"></i>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{recipes.length === 0 && (
|
|
||||||
<div className="empty-category">
|
|
||||||
No blueprints available in this sector.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CraftModal
|
<CraftModal
|
||||||
recipe={selectedRecipe}
|
recipe={selectedRecipe}
|
||||||
onClose={() => setSelectedRecipe(null)}
|
onClose={() => !activeCraft && setSelectedRecipe(null)}
|
||||||
onStartCraft={handleStartCrafting}
|
onStartCraft={handleStartCrafting}
|
||||||
isBusy={!!activeCraft}
|
activeCraft={activeCraft}
|
||||||
getOwnedAmount={getOwnedAmount}
|
getOwnedAmount={getOwnedAmount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,15 +5,13 @@ const CraftModal = ({
|
|||||||
recipe,
|
recipe,
|
||||||
onClose,
|
onClose,
|
||||||
onStartCraft,
|
onStartCraft,
|
||||||
isBusy,
|
activeCraft,
|
||||||
getOwnedAmount,
|
getOwnedAmount,
|
||||||
}) => {
|
}) => {
|
||||||
if (!recipe) return null;
|
if (!recipe) return null;
|
||||||
|
|
||||||
const displayName = recipe.resultItem?.name || recipe.name || recipe.id;
|
const isBusy = !!activeCraft;
|
||||||
const craftTime = recipe.constructionTime || recipe.time || 0;
|
const outputQty = Object.values(recipe.output || {})[0] || 1;
|
||||||
|
|
||||||
// Перевірка, чи вистачає всіх ресурсів для крафту
|
|
||||||
const canAfford = recipe.ingredients?.every(
|
const canAfford = recipe.ingredients?.every(
|
||||||
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
|
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
|
||||||
);
|
);
|
||||||
@ -23,7 +21,7 @@ const CraftModal = ({
|
|||||||
<div className="craft-modal" onClick={(e) => e.stopPropagation()}>
|
<div className="craft-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h3>
|
<h3>
|
||||||
<i className="fas fa-tools"></i> Construction: {displayName}
|
<i className="fas fa-tools"></i> Construction: {recipe.displayName}
|
||||||
</h3>
|
</h3>
|
||||||
<button className="close-x" onClick={onClose}>
|
<button className="close-x" onClick={onClose}>
|
||||||
×
|
×
|
||||||
@ -36,8 +34,7 @@ const CraftModal = ({
|
|||||||
<i className="fas fa-list-ul"></i> Required Resources
|
<i className="fas fa-list-ul"></i> Required Resources
|
||||||
</h4>
|
</h4>
|
||||||
<div className="res-grid">
|
<div className="res-grid">
|
||||||
{recipe.ingredients &&
|
{recipe.ingredients?.map((ing) => {
|
||||||
recipe.ingredients.map((ing) => {
|
|
||||||
const owned = getOwnedAmount(ing.itemId);
|
const owned = getOwnedAmount(ing.itemId);
|
||||||
const hasEnough = owned >= ing.quantity;
|
const hasEnough = owned >= ing.quantity;
|
||||||
|
|
||||||
@ -50,9 +47,7 @@ const CraftModal = ({
|
|||||||
<i
|
<i
|
||||||
className={`fas fa-cube ${hasEnough ? "icon-green" : "icon-red"}`}
|
className={`fas fa-cube ${hasEnough ? "icon-green" : "icon-red"}`}
|
||||||
></i>
|
></i>
|
||||||
<span className="res-name">
|
<span className="res-name">{ing.displayName}</span>
|
||||||
{ing.name || ing.itemId.replace("_", " ")}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="res-quantity-info">
|
<div className="res-quantity-info">
|
||||||
<span
|
<span
|
||||||
@ -76,18 +71,34 @@ const CraftModal = ({
|
|||||||
<div className="outcome-row">
|
<div className="outcome-row">
|
||||||
<span>Result:</span>
|
<span>Result:</span>
|
||||||
<strong>
|
<strong>
|
||||||
{displayName} x{recipe.result?.quantity || 1}
|
{recipe.displayName} x{outputQty}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="outcome-row">
|
<div className="outcome-row">
|
||||||
<span>Time:</span>
|
<span>Time:</span>
|
||||||
<strong>{craftTime}s</strong>
|
<strong>{recipe.time_seconds}s</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
|
{activeCraft && activeCraft.recipeId === recipe.id ? (
|
||||||
|
<div className="modal-progress-container">
|
||||||
|
<div className="progress-text">
|
||||||
|
Processing... {activeCraft.timeLeft}s
|
||||||
|
</div>
|
||||||
|
<div className="progress-bar-bg">
|
||||||
|
<div
|
||||||
|
className="progress-bar-fill"
|
||||||
|
style={{
|
||||||
|
width: `${((activeCraft.totalTime - activeCraft.timeLeft) / activeCraft.totalTime) * 100}%`,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
className={`btn-start-craft ${!canAfford || isBusy ? "disabled" : ""}`}
|
className={`btn-start-craft ${!canAfford || isBusy ? "disabled" : ""}`}
|
||||||
onClick={() => canAfford && !isBusy && onStartCraft(recipe)}
|
onClick={() => canAfford && !isBusy && onStartCraft(recipe)}
|
||||||
@ -97,11 +108,13 @@ const CraftModal = ({
|
|||||||
? "System Busy..."
|
? "System Busy..."
|
||||||
: !canAfford
|
: !canAfford
|
||||||
? "Insufficient Resources"
|
? "Insufficient Resources"
|
||||||
: `Start Construction (${craftTime}s)`}
|
: `Start Construction (${recipe.time_seconds}s)`}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn-cancel" onClick={onClose}>
|
<button className="btn-cancel" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
{
|
{
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"inputs": [
|
"inputs": [{ "original:ingot_iron": 1 }, { "original:ore_coal": 5 }],
|
||||||
{ "original:ingot_iron": 1 },
|
|
||||||
{ "original:ore_coal": 5 }
|
|
||||||
],
|
|
||||||
"output": {
|
"output": {
|
||||||
"original:alloy_steel": 1
|
"original:alloy_steel": 1
|
||||||
},
|
},
|
||||||
"time_seconds": 180,
|
"time_seconds": 10,
|
||||||
"requires": {
|
"requires": {
|
||||||
"original:alloying": 0
|
"original:alloying": 0
|
||||||
}
|
}
|
||||||
|
|||||||
71
game-server/src/game/CraftManager.js
Normal file
71
game-server/src/game/CraftManager.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const datapackLoader = require("../game/DatapackLoader");
|
||||||
|
const { Inventory } = require("../models");
|
||||||
|
|
||||||
|
class CraftManager {
|
||||||
|
constructor() {
|
||||||
|
this.activeCrafts = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
async startCraft(userId, recipeId, socket) {
|
||||||
|
if (this.activeCrafts.has(userId)) return { error: "Already crafting" };
|
||||||
|
|
||||||
|
const recipe = datapackLoader.getRecipe(recipeId);
|
||||||
|
if (!recipe) return { error: "Recipe not found" };
|
||||||
|
|
||||||
|
const craftTimeMs = (recipe.time_seconds || 0) * 1000;
|
||||||
|
const finishAt = Date.now() + craftTimeMs;
|
||||||
|
|
||||||
|
const craftData = {
|
||||||
|
recipeId,
|
||||||
|
finishAt,
|
||||||
|
totalTime: recipe.time_seconds,
|
||||||
|
timer: setTimeout(
|
||||||
|
() => this.completeCraft(userId, recipeId, socket),
|
||||||
|
craftTimeMs,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.activeCrafts.set(userId, craftData);
|
||||||
|
return { recipeId, finishAt, totalTime: recipe.time_seconds };
|
||||||
|
}
|
||||||
|
|
||||||
|
async completeCraft(userId, recipeId, socket) {
|
||||||
|
try {
|
||||||
|
const recipe = datapackLoader.getRecipe(recipeId);
|
||||||
|
const outputItemId = Object.keys(recipe.output)[0];
|
||||||
|
const outputQuantity = recipe.output[outputItemId];
|
||||||
|
|
||||||
|
const [newItem, created] = await Inventory.findOrCreate({
|
||||||
|
where: { playerId: userId, itemId: outputItemId },
|
||||||
|
defaults: { quantity: outputQuantity },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!created) {
|
||||||
|
await newItem.increment("quantity", { by: outputQuantity });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeCrafts.delete(userId);
|
||||||
|
|
||||||
|
if (socket && socket.connected) {
|
||||||
|
socket.emit("player:craft_success", { recipeId });
|
||||||
|
socket.emit("player:get_inventory");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Complete craft error:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getExistingCraft(userId) {
|
||||||
|
const craft = this.activeCrafts.get(userId);
|
||||||
|
if (craft && craft.finishAt > Date.now()) {
|
||||||
|
return {
|
||||||
|
recipeId: craft.recipeId,
|
||||||
|
finishAt: craft.finishAt,
|
||||||
|
totalTime: craft.totalTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new CraftManager();
|
||||||
@ -73,7 +73,22 @@ class DatapackLoader {
|
|||||||
`🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.languages.size} Langs, ${manifestCount} Manifests`,
|
`🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.languages.size} Langs, ${manifestCount} Manifests`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
getRecipe(id) {
|
||||||
|
return this.registry.recipes.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecipesByCategory(category) {
|
||||||
|
const allRecipes = Array.from(this.registry.recipes.values());
|
||||||
|
return allRecipes.filter((r) => r.category === category);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecipeCategories() {
|
||||||
|
const allRecipes = Array.from(this.registry.recipes.values());
|
||||||
|
const categories = new Set(
|
||||||
|
allRecipes.map((r) => r.category).filter(Boolean),
|
||||||
|
);
|
||||||
|
return Array.from(categories);
|
||||||
|
}
|
||||||
loadLanguages(langPath) {
|
loadLanguages(langPath) {
|
||||||
try {
|
try {
|
||||||
const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
|
const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
|
||||||
|
|||||||
@ -1,80 +1,89 @@
|
|||||||
const datapackLoader = require("../../game/DatapackLoader");
|
const datapackLoader = require("../../game/DatapackLoader");
|
||||||
const { Inventory } = require("../../models");
|
const { Inventory } = require("../../models");
|
||||||
|
const craftManager = require("../../game/CraftManager");
|
||||||
|
|
||||||
module.exports = (io, socket) => {
|
module.exports = (io, socket) => {
|
||||||
const userId = socket.user?.id;
|
const userId = socket.user?.id;
|
||||||
|
|
||||||
|
const sendStatus = () => {
|
||||||
|
const existing = craftManager.getExistingCraft(userId);
|
||||||
|
if (existing) {
|
||||||
|
socket.emit("player:craft_started", existing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on("player:check_active_craft", () => {
|
||||||
|
sendStatus();
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("player:get_recipe_categories", () => {
|
socket.on("player:get_recipe_categories", () => {
|
||||||
try {
|
try {
|
||||||
const categories = datapackLoader.getRecipeCategories();
|
const categories = datapackLoader.getRecipeCategories();
|
||||||
socket.emit("player:recipe_categories_data", categories);
|
socket.emit("player:recipe_categories_data", categories);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error getting categories:", err.message);
|
console.error(err.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("player:get_recipes", ({ category }) => {
|
socket.on("player:get_recipes", ({ category }) => {
|
||||||
try {
|
try {
|
||||||
if (!category) return;
|
if (!category) return;
|
||||||
|
|
||||||
const rawRecipes = datapackLoader.getRecipesByCategory(category);
|
const rawRecipes = datapackLoader.getRecipesByCategory(category);
|
||||||
|
|
||||||
const recipeIds = rawRecipes.map((r) => r.id);
|
const recipeIds = rawRecipes.map((r) => r.id);
|
||||||
|
socket.emit("player:recipes_data", { category, recipeIds });
|
||||||
socket.emit("player:recipes_data", {
|
|
||||||
category,
|
|
||||||
recipeIds: recipeIds,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching recipes:", err.message);
|
console.error(err.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("player:craft_item", async ({ recipeId, category }) => {
|
socket.on("player:craft_item", async ({ recipeId }) => {
|
||||||
try {
|
try {
|
||||||
const recipe = datapackLoader.getRecipe(category, recipeId);
|
if (craftManager.getExistingCraft(userId)) {
|
||||||
|
return socket.emit("error", { message: "Already crafting" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipe = datapackLoader.getRecipe(recipeId);
|
||||||
if (!recipe) return socket.emit("error", { message: "Recipe not found" });
|
if (!recipe) return socket.emit("error", { message: "Recipe not found" });
|
||||||
|
|
||||||
for (const ing of recipe.ingredients) {
|
for (const ing of recipe.inputs) {
|
||||||
|
const itemId = Object.keys(ing)[0];
|
||||||
|
const quantity = ing[itemId];
|
||||||
const invItem = await Inventory.findOne({
|
const invItem = await Inventory.findOne({
|
||||||
where: { playerId: userId, itemId: ing.itemId },
|
where: { playerId: userId, itemId: itemId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!invItem || invItem.quantity < ing.quantity) {
|
if (!invItem || invItem.quantity < quantity) {
|
||||||
return socket.emit("error", {
|
return socket.emit("error", { message: `Not enough resources` });
|
||||||
message: `Недостатньо ресурсів для ${recipeId}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const ing of recipe.ingredients) {
|
for (const ing of recipe.inputs) {
|
||||||
|
const itemId = Object.keys(ing)[0];
|
||||||
|
const quantity = ing[itemId];
|
||||||
const invItem = await Inventory.findOne({
|
const invItem = await Inventory.findOne({
|
||||||
where: { playerId: userId, itemId: ing.itemId },
|
where: { playerId: userId, itemId: itemId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (invItem.quantity === ing.quantity) {
|
if (invItem.quantity === quantity) {
|
||||||
await invItem.destroy();
|
await invItem.destroy();
|
||||||
} else {
|
} else {
|
||||||
await invItem.decrement("quantity", { by: ing.quantity });
|
await invItem.decrement("quantity", { by: quantity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [newItem, created] = await Inventory.findOrCreate({
|
const result = await craftManager.startCraft(userId, recipeId, socket);
|
||||||
where: { playerId: userId, itemId: recipe.id },
|
|
||||||
defaults: { quantity: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!created) {
|
if (result.error) {
|
||||||
await newItem.increment("quantity", { by: 1 });
|
return socket.emit("error", { message: result.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit("player:craft_success", { recipeId });
|
socket.emit("player:craft_started", result);
|
||||||
|
|
||||||
socket.emit("player:get_inventory");
|
socket.emit("player:get_inventory");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Crafting error:", err.message);
|
console.error(err.message);
|
||||||
socket.emit("error", { message: "Internal Crafting Error" });
|
socket.emit("error", { message: "Internal Crafting Error" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sendStatus();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user