This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/GameServer/systems/CraftingSystem.js
2026-03-10 11:20:02 -03:00

161 lines
5.3 KiB
JavaScript

/**
* CraftingSystem — recipe lookup + craft execution with inventory checks.
* GDD §11: Crafting
*/
class CraftingSystem {
constructor(contentLoader) {
this.loader = contentLoader;
this.playerCrafting = new Map();
}
getAllRecipes() { return this.loader.getAllRecipes(); }
getRecipe(id) { return this.loader.getRecipe(id); }
getRecipesByType(type) { return this.loader.getRecipesByType(type); }
getCraftingTabs() { return this.loader.getCraftingTabs(); }
initializePlayerData(userId) {
if (this.playerCrafting.has(userId)) return this.playerCrafting.get(userId);
const data = { skill: 1, experience: 0, knownRecipes: new Set(), totalCrafted: 0 };
this.playerCrafting.set(userId, data);
return data;
}
getPlayerCrafting(userId) { return this.initializePlayerData(userId); }
/**
* Check if player has all required materials in inventory.
* Returns { canCraft: bool, missing: [{itemId, need, have}] }
*/
checkMaterials(recipeId, playerInventory) {
const recipe = this.getRecipe(recipeId);
if (!recipe) return { canCraft: false, missing: [{ itemId: 'unknown', need: 1, have: 0 }] };
const inputs = recipe.recipe?.inputs || recipe.inputs || {};
const missing = [];
for (const [itemId, qty] of Object.entries(inputs)) {
const have = this._countItem(playerInventory, itemId);
if (have < qty) {
missing.push({ itemId, need: qty, have });
}
}
return { canCraft: missing.length === 0, missing };
}
/**
* Execute a craft: consume materials, add output item, return result.
* Returns { success, output, xpGained, error? }
*/
executeCraft(recipeId, playerData, userId) {
const recipe = this.getRecipe(recipeId);
if (!recipe) return { success: false, error: 'Recipe not found' };
const inputs = recipe.recipe?.inputs || recipe.inputs || {};
const outputs = recipe.recipe?.output || recipe.output || {};
const xpGain = recipe.craft?.xp || recipe.xp || 10;
const inventory = playerData.inventory || { items: [] };
// Re-check materials server-side
const check = this.checkMaterials(recipeId, inventory);
if (!check.canCraft) {
return { success: false, error: 'Missing materials', missing: check.missing };
}
// Consume inputs
for (const [itemId, qty] of Object.entries(inputs)) {
this._removeItems(inventory, itemId, qty);
}
// Add outputs
const outputItems = [];
for (const [itemId, qty] of Object.entries(outputs)) {
const item = this._makeItem(itemId, qty);
inventory.items.push(item);
outputItems.push(item);
}
// Update crafting stats
const pd = this.initializePlayerData(userId);
pd.totalCrafted++;
pd.experience += xpGain;
const newSkill = this._calcSkillLevel(pd.experience);
if (newSkill > pd.skill) pd.skill = newSkill;
pd.knownRecipes.add(recipeId);
// Persist crafting data back into playerData
playerData.crafting = {
...(playerData.crafting || {}),
skill: pd.skill,
experience: pd.experience,
totalCrafted: pd.totalCrafted,
};
this.recordCraft(userId, recipeId);
return {
success: true,
output: outputItems,
xpGained: xpGain,
craftingLevel: pd.skill,
craftingXp: pd.experience,
};
}
recordCraft(userId, recipeId) {
const pd = this.initializePlayerData(userId);
pd.totalCrafted++;
pd.knownRecipes.add(recipeId);
}
// ── Private helpers ──────────────────────────────────────────────────
_countItem(inventory, itemId) {
const items = (inventory && inventory.items) ? inventory.items : inventory || [];
let total = 0;
for (const it of items) {
if (it && (it.id === itemId || it.itemId === itemId)) {
total += (it.quantity || it.qty || 1);
}
}
return total;
}
_removeItems(inventory, itemId, qty) {
let remaining = qty;
const items = inventory.items || [];
for (let i = items.length - 1; i >= 0 && remaining > 0; i--) {
const it = items[i];
if (!it) continue;
if (it.id === itemId || it.itemId === itemId) {
const have = it.quantity || it.qty || 1;
if (have <= remaining) {
remaining -= have;
items.splice(i, 1);
} else {
it.quantity = have - remaining;
remaining = 0;
}
}
}
}
_makeItem(itemId, qty) {
return {
id: itemId,
itemId,
quantity: qty,
obtainedAt: Date.now(),
source: 'crafting',
};
}
_calcSkillLevel(xp) {
// Every 200 XP = 1 crafting level, cap at 50
return Math.min(50, Math.floor(xp / 200) + 1);
}
}
module.exports = CraftingSystem;