710 lines
26 KiB
JavaScript
710 lines
26 KiB
JavaScript
/**
|
|
* Galaxy Strike Online - Item System
|
|
* Centralized item management for shop, inventory, dungeons, and all game systems
|
|
*/
|
|
|
|
class ItemSystem {
|
|
constructor() {
|
|
// Server configuration - will be set dynamically
|
|
this.serverUrl = process.env.SERVER_URL || 'http://localhost:3002';
|
|
|
|
// Master item catalog - single source of truth
|
|
this.itemCatalog = {
|
|
// Ships
|
|
ships: [
|
|
{
|
|
id: 'starter_cruiser_common',
|
|
name: 'Starter Cruiser',
|
|
type: 'ship',
|
|
rarity: 'common',
|
|
price: 5000,
|
|
currency: 'credits',
|
|
description: 'Reliable starter cruiser for new pilots',
|
|
texturePath: 'images/ships/starter_cruiser_common.png',
|
|
stats: { attack: 15, speed: 10, defense: 12, hull: 100 },
|
|
categories: ['shop', 'dungeon_reward'],
|
|
requirements: { level: 1 },
|
|
stackable: false
|
|
},
|
|
{
|
|
id: 'starter_cruiser_uncommon',
|
|
name: 'Starter Cruiser II',
|
|
type: 'ship',
|
|
rarity: 'uncommon',
|
|
price: 12000,
|
|
currency: 'credits',
|
|
description: 'Upgraded starter cruiser with enhanced systems',
|
|
texturePath: 'images/ships/starter_cruiser_uncommon.png',
|
|
stats: { attack: 18, speed: 12, defense: 15, hull: 120 },
|
|
categories: ['shop', 'dungeon_reward'],
|
|
requirements: { level: 5 },
|
|
stackable: false
|
|
},
|
|
{
|
|
id: 'starter_cruiser_rare',
|
|
name: 'Starter Cruiser III',
|
|
type: 'ship',
|
|
rarity: 'rare',
|
|
price: 25000,
|
|
currency: 'credits',
|
|
description: 'Elite starter cruiser with maximum upgrades',
|
|
texturePath: 'images/ships/starter_cruiser_rare.png',
|
|
stats: { attack: 22, speed: 15, defense: 18, hull: 150 },
|
|
categories: ['shop', 'dungeon_reward'],
|
|
requirements: { level: 10 },
|
|
stackable: false
|
|
},
|
|
{
|
|
id: 'interceptor_common',
|
|
name: 'Interceptor',
|
|
type: 'ship',
|
|
rarity: 'common',
|
|
price: 8000,
|
|
currency: 'credits',
|
|
description: 'Fast attack ship for hit-and-run tactics',
|
|
texturePath: 'images/ships/interceptor_common.png',
|
|
stats: { attack: 12, speed: 18, defense: 8, hull: 80 },
|
|
categories: ['shop', 'dungeon_reward'],
|
|
requirements: { level: 3 },
|
|
stackable: false
|
|
},
|
|
{
|
|
id: 'interceptor_uncommon',
|
|
name: 'Interceptor II',
|
|
type: 'ship',
|
|
rarity: 'uncommon',
|
|
price: 18000,
|
|
currency: 'credits',
|
|
description: 'Enhanced interceptor with improved weapons',
|
|
texturePath: 'images/ships/interceptor_uncommon.png',
|
|
stats: { attack: 15, speed: 22, defense: 10, hull: 95 },
|
|
categories: ['shop', 'dungeon_reward'],
|
|
requirements: { level: 7 },
|
|
stackable: false
|
|
}
|
|
],
|
|
|
|
// Materials
|
|
materials: [
|
|
{
|
|
id: 'steel_plating',
|
|
name: 'Steel Plating',
|
|
type: 'material',
|
|
rarity: 'common',
|
|
price: 100,
|
|
currency: 'credits',
|
|
description: 'Basic armor material for ship upgrades',
|
|
texture: 'http://localhost:3002/images/items/materials/steel_plating.png',
|
|
categories: ['shop', 'dungeon_loot', 'crafting'],
|
|
requirements: { level: 1 },
|
|
stackable: true,
|
|
maxStack: 99,
|
|
effects: { defense: 5 }
|
|
},
|
|
{
|
|
id: 'energy_core',
|
|
name: 'Energy Core',
|
|
type: 'material',
|
|
rarity: 'uncommon',
|
|
price: 250,
|
|
currency: 'credits',
|
|
description: 'Power source for advanced upgrades',
|
|
texture: 'http://localhost:3002/images/items/materials/energy_crystal.png',
|
|
categories: ['shop', 'dungeon_loot', 'crafting'],
|
|
requirements: { level: 5 },
|
|
stackable: true,
|
|
maxStack: 50,
|
|
effects: { energy: 10 }
|
|
},
|
|
{
|
|
id: 'quantum_fuel',
|
|
name: 'Quantum Fuel',
|
|
type: 'material',
|
|
rarity: 'rare',
|
|
price: 500,
|
|
currency: 'credits',
|
|
description: 'Advanced fuel for long-range travel',
|
|
texture: 'http://localhost:3002/images/items/materials/quantum_core.png',
|
|
categories: ['shop', 'dungeon_loot', 'crafting'],
|
|
requirements: { level: 10 },
|
|
stackable: true,
|
|
maxStack: 25,
|
|
effects: { speed: 15 }
|
|
},
|
|
{
|
|
id: 'dark_matter',
|
|
name: 'Dark Matter',
|
|
type: 'material',
|
|
rarity: 'legendary',
|
|
price: 2000,
|
|
currency: 'gems',
|
|
description: 'Exotic matter for ultimate upgrades',
|
|
texture: 'http://localhost:3002/images/items/materials/dark_matter_fragment.png',
|
|
categories: ['shop', 'dungeon_loot', 'crafting'],
|
|
requirements: { level: 15 },
|
|
stackable: true,
|
|
maxStack: 10,
|
|
effects: { attack: 20, defense: 20 }
|
|
}
|
|
],
|
|
|
|
// Consumables
|
|
consumables: [
|
|
{
|
|
id: 'repair_kit',
|
|
name: 'Repair Kit',
|
|
type: 'consumable',
|
|
rarity: 'common',
|
|
price: 50,
|
|
currency: 'credits',
|
|
description: 'Instantly repairs ship damage',
|
|
texture: 'http://localhost:3002/images/items/consumables/repair_kit.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 1 },
|
|
stackable: true,
|
|
maxStack: 20,
|
|
effects: { hull_repair: 50 },
|
|
consumable: true,
|
|
cooldown: 30000 // 30 seconds
|
|
},
|
|
{
|
|
id: 'energy_boost',
|
|
name: 'Energy Boost',
|
|
type: 'consumable',
|
|
rarity: 'common',
|
|
price: 75,
|
|
currency: 'credits',
|
|
description: 'Temporary energy increase',
|
|
texture: 'http://localhost:3002/images/items/consumables/energy_boost.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 1 },
|
|
stackable: true,
|
|
maxStack: 15,
|
|
effects: { energy_boost: 25, duration: 60000 }, // 1 minute
|
|
consumable: true,
|
|
cooldown: 45000 // 45 seconds
|
|
},
|
|
{
|
|
id: 'shield_booster',
|
|
name: 'Shield Booster',
|
|
type: 'consumable',
|
|
rarity: 'uncommon',
|
|
price: 150,
|
|
currency: 'credits',
|
|
description: 'Temporary shield enhancement',
|
|
texture: 'http://localhost:3002/images/items/consumables/shield_recharge.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 5 },
|
|
stackable: true,
|
|
maxStack: 10,
|
|
effects: { shield_boost: 50, duration: 90000 }, // 1.5 minutes
|
|
consumable: true,
|
|
cooldown: 60000 // 1 minute
|
|
},
|
|
{
|
|
id: 'damage_amplifier',
|
|
name: 'Damage Amplifier',
|
|
type: 'consumable',
|
|
rarity: 'rare',
|
|
price: 300,
|
|
currency: 'credits',
|
|
description: 'Increases damage output temporarily',
|
|
texture: 'http://localhost:3002/images/items/consumables/ammo_pack.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 8 },
|
|
stackable: true,
|
|
maxStack: 5,
|
|
effects: { damage_multiplier: 1.5, duration: 45000 }, // 45 seconds
|
|
consumable: true,
|
|
cooldown: 120000 // 2 minutes
|
|
}
|
|
],
|
|
|
|
// Cosmetics
|
|
cosmetics: [
|
|
{
|
|
id: 'red_paint',
|
|
name: 'Red Paint Job',
|
|
type: 'cosmetic',
|
|
rarity: 'common',
|
|
price: 500,
|
|
currency: 'credits',
|
|
description: 'Red color scheme for your ship',
|
|
texture: 'http://localhost:3002/images/items/cosmetics/cool_paint_job.png',
|
|
categories: ['shop'],
|
|
requirements: { level: 1 },
|
|
stackable: false,
|
|
cosmetic: true,
|
|
slot: 'paint'
|
|
},
|
|
{
|
|
id: 'blue_paint',
|
|
name: 'Blue Paint Job',
|
|
type: 'cosmetic',
|
|
rarity: 'common',
|
|
price: 500,
|
|
currency: 'credits',
|
|
description: 'Blue color scheme for your ship',
|
|
texture: 'http://localhost:3002/images/items/cosmetics/neon_lights.png',
|
|
categories: ['shop'],
|
|
requirements: { level: 1 },
|
|
stackable: false,
|
|
cosmetic: true,
|
|
slot: 'paint'
|
|
},
|
|
{
|
|
id: 'gold_trim',
|
|
name: 'Gold Trim',
|
|
type: 'cosmetic',
|
|
rarity: 'uncommon',
|
|
price: 1000,
|
|
currency: 'credits',
|
|
description: 'Gold accent trim for your ship',
|
|
texture: 'http://localhost:3002/images/items/cosmetics/golden_trim.png',
|
|
categories: ['shop'],
|
|
requirements: { level: 5 },
|
|
stackable: false,
|
|
cosmetic: true,
|
|
slot: 'trim'
|
|
},
|
|
{
|
|
id: 'rainbow_effect',
|
|
name: 'Rainbow Engine Effect',
|
|
type: 'cosmetic',
|
|
rarity: 'rare',
|
|
price: 2500,
|
|
currency: 'credits',
|
|
description: 'Colorful engine trail effect',
|
|
texture: 'http://localhost:3002/images/items/cosmetics/custom_decal.png',
|
|
categories: ['shop'],
|
|
requirements: { level: 10 },
|
|
stackable: false,
|
|
cosmetic: true,
|
|
slot: 'engine'
|
|
}
|
|
],
|
|
|
|
// Weapons
|
|
weapons: [
|
|
{
|
|
id: 'laser_cannon_common',
|
|
name: 'Laser Cannon',
|
|
type: 'weapon',
|
|
rarity: 'common',
|
|
price: 1000,
|
|
currency: 'credits',
|
|
description: 'Basic laser weapon for beginners',
|
|
texture: 'http://localhost:3002/images/weapons/laser_pistol_common.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 1 },
|
|
stackable: false,
|
|
stats: { attack: 10, range: 100, fire_rate: 2 }
|
|
},
|
|
{
|
|
id: 'plasma_rifle_uncommon',
|
|
name: 'Plasma Rifle',
|
|
type: 'weapon',
|
|
rarity: 'uncommon',
|
|
price: 2500,
|
|
currency: 'credits',
|
|
description: 'Plasma-based weapon with increased damage',
|
|
texture: 'http://localhost:3002/images/weapons/plasma_rifle_uncommon.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 5 },
|
|
stackable: false,
|
|
stats: { attack: 18, range: 120, fire_rate: 1.5 }
|
|
},
|
|
{
|
|
id: 'quantum_blaster_rare',
|
|
name: 'Quantum Blaster',
|
|
type: 'weapon',
|
|
rarity: 'rare',
|
|
price: 6000,
|
|
currency: 'credits',
|
|
description: 'Advanced quantum weapon with high damage output',
|
|
texture: 'http://localhost:3002/images/weapons/plasma_rifle_rare.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 10 },
|
|
stackable: false,
|
|
stats: { attack: 30, range: 150, fire_rate: 1 }
|
|
}
|
|
],
|
|
|
|
// Armors
|
|
armors: [
|
|
{
|
|
id: 'basic_shield_common',
|
|
name: 'Basic Shield Generator',
|
|
type: 'armor',
|
|
rarity: 'common',
|
|
price: 800,
|
|
currency: 'credits',
|
|
description: 'Basic shield protection for beginners',
|
|
texture: 'http://localhost:3002/images/armors/light_armor_common.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 1 },
|
|
stackable: false,
|
|
stats: { defense: 8, shield_capacity: 50, recharge_rate: 5 }
|
|
},
|
|
{
|
|
id: 'energy_armor_uncommon',
|
|
name: 'Energy Armor',
|
|
type: 'armor',
|
|
rarity: 'uncommon',
|
|
price: 2000,
|
|
currency: 'credits',
|
|
description: 'Energy-based armor with enhanced protection',
|
|
texture: 'http://localhost:3002/images/armors/light_armor_uncommon.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 5 },
|
|
stackable: false,
|
|
stats: { defense: 15, shield_capacity: 100, recharge_rate: 8 }
|
|
},
|
|
{
|
|
id: 'quantum_deflector_rare',
|
|
name: 'Quantum Deflector',
|
|
type: 'armor',
|
|
rarity: 'rare',
|
|
price: 5000,
|
|
currency: 'credits',
|
|
description: 'Advanced quantum armor with maximum protection',
|
|
texture: 'http://localhost:3002/images/armors/light_armor_rare.png',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 10 },
|
|
stackable: false,
|
|
stats: { defense: 25, shield_capacity: 200, recharge_rate: 12 }
|
|
}
|
|
],
|
|
|
|
// Dungeon-specific rewards
|
|
dungeon_rewards: [
|
|
{
|
|
id: 'dungeon_key_basic',
|
|
name: 'Basic Dungeon Key',
|
|
type: 'key',
|
|
rarity: 'common',
|
|
price: 200,
|
|
currency: 'credits',
|
|
description: 'Key to enter basic dungeons',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 1 },
|
|
stackable: true,
|
|
maxStack: 10,
|
|
consumable: true,
|
|
dungeon_access: ['basic_dungeon', 'mines']
|
|
},
|
|
{
|
|
id: 'dungeon_key_advanced',
|
|
name: 'Advanced Dungeon Key',
|
|
type: 'key',
|
|
rarity: 'uncommon',
|
|
price: 800,
|
|
currency: 'credits',
|
|
description: 'Key to enter advanced dungeons',
|
|
categories: ['shop', 'dungeon_loot'],
|
|
requirements: { level: 8 },
|
|
stackable: true,
|
|
maxStack: 5,
|
|
consumable: true,
|
|
dungeon_access: ['advanced_dungeon', 'fortress']
|
|
},
|
|
{
|
|
id: 'treasure_chest',
|
|
name: 'Treasure Chest',
|
|
type: 'container',
|
|
rarity: 'rare',
|
|
price: 0,
|
|
currency: 'credits',
|
|
description: 'Contains random valuable items',
|
|
categories: ['dungeon_loot'],
|
|
requirements: { level: 1 },
|
|
stackable: true,
|
|
maxStack: 5,
|
|
consumable: true,
|
|
loot_table: 'treasure_chest_common'
|
|
}
|
|
]
|
|
};
|
|
|
|
// Initialize item lookup maps for performance
|
|
this.itemLookup = new Map();
|
|
this.categoryLookup = new Map();
|
|
this.rarityLookup = new Map();
|
|
|
|
this.buildLookupMaps();
|
|
}
|
|
|
|
/**
|
|
* Build lookup maps for efficient item retrieval
|
|
*/
|
|
buildLookupMaps() {
|
|
// Build ID lookup
|
|
for (const [category, items] of Object.entries(this.itemCatalog)) {
|
|
for (const item of items) {
|
|
this.itemLookup.set(item.id, item);
|
|
|
|
// Build category lookup
|
|
if (!this.categoryLookup.has(category)) {
|
|
this.categoryLookup.set(category, []);
|
|
}
|
|
this.categoryLookup.get(category).push(item);
|
|
|
|
// Build rarity lookup
|
|
if (!this.rarityLookup.has(item.rarity)) {
|
|
this.rarityLookup.set(item.rarity, []);
|
|
}
|
|
this.rarityLookup.get(item.rarity).push(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get item by ID
|
|
*/
|
|
getItem(itemId) {
|
|
return this.itemLookup.get(itemId) || null;
|
|
}
|
|
|
|
/**
|
|
* Get all items in a category
|
|
*/
|
|
getItemsByCategory(category) {
|
|
return this.categoryLookup.get(category) || [];
|
|
}
|
|
|
|
/**
|
|
* Get items by rarity
|
|
*/
|
|
getItemsByRarity(rarity) {
|
|
return this.rarityLookup.get(rarity) || [];
|
|
}
|
|
|
|
/**
|
|
* Get items available for specific context (shop, dungeon, etc.)
|
|
*/
|
|
getItemsByContext(context) {
|
|
const results = [];
|
|
|
|
for (const item of this.itemLookup.values()) {
|
|
if (item.categories && item.categories.includes(context)) {
|
|
results.push(item);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Get shop items (items available in shop)
|
|
*/
|
|
getShopItems() {
|
|
return this.getItemsByContext('shop');
|
|
}
|
|
|
|
/**
|
|
* Get dungeon loot items
|
|
*/
|
|
getDungeonLootItems() {
|
|
return this.getItemsByContext('dungeon_loot');
|
|
}
|
|
|
|
/**
|
|
* Get dungeon reward items
|
|
*/
|
|
getDungeonRewardItems() {
|
|
return this.getItemsByContext('dungeon_reward');
|
|
}
|
|
|
|
/**
|
|
* Get crafting materials
|
|
*/
|
|
getCraftingMaterials() {
|
|
return this.getItemsByContext('crafting');
|
|
}
|
|
|
|
/**
|
|
* Check if player meets item requirements
|
|
*/
|
|
canPlayerUseItem(playerData, item) {
|
|
if (!item.requirements) return true;
|
|
|
|
// Check level requirement
|
|
if (item.requirements.level && playerData.stats.level < item.requirements.level) {
|
|
return false;
|
|
}
|
|
|
|
// Add other requirement checks here (skills, quests, etc.)
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get filtered items for player (based on requirements)
|
|
*/
|
|
getAvailableItemsForPlayer(playerData, context = null) {
|
|
let items = context ? this.getItemsByContext(context) : Array.from(this.itemLookup.values());
|
|
|
|
return items.filter(item => this.canPlayerUseItem(playerData, item));
|
|
}
|
|
|
|
/**
|
|
* Generate random loot based on rarity weights
|
|
*/
|
|
generateLoot(rarityWeights = null, context = null) {
|
|
const defaultWeights = {
|
|
common: 50,
|
|
uncommon: 30,
|
|
rare: 15,
|
|
legendary: 5
|
|
};
|
|
|
|
const weights = rarityWeights || defaultWeights;
|
|
const availableItems = context ? this.getItemsByContext(context) : Array.from(this.itemLookup.values());
|
|
|
|
// Filter by rarity weights
|
|
const weightedItems = [];
|
|
for (const item of availableItems) {
|
|
const weight = weights[item.rarity] || 0;
|
|
for (let i = 0; i < weight; i++) {
|
|
weightedItems.push(item);
|
|
}
|
|
}
|
|
|
|
if (weightedItems.length === 0) return null;
|
|
|
|
// Random selection
|
|
const randomIndex = Math.floor(Math.random() * weightedItems.length);
|
|
return weightedItems[randomIndex];
|
|
}
|
|
|
|
/**
|
|
* Get item statistics
|
|
*/
|
|
getItemStats() {
|
|
const stats = {
|
|
totalItems: this.itemLookup.size,
|
|
byCategory: {},
|
|
byRarity: {},
|
|
byType: {}
|
|
};
|
|
|
|
for (const item of this.itemLookup.values()) {
|
|
// Count by category
|
|
for (const category of item.categories || []) {
|
|
stats.byCategory[category] = (stats.byCategory[category] || 0) + 1;
|
|
}
|
|
|
|
// Count by rarity
|
|
stats.byRarity[item.rarity] = (stats.byRarity[item.rarity] || 0) + 1;
|
|
|
|
// Count by type
|
|
stats.byType[item.type] = (stats.byType[item.type] || 0) + 1;
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Validate item data structure
|
|
*/
|
|
validateItem(item) {
|
|
const required = ['id', 'name', 'type', 'rarity'];
|
|
const missing = required.filter(field => !item[field]);
|
|
|
|
if (missing.length > 0) {
|
|
return { valid: false, errors: [`Missing required fields: ${missing.join(', ')}`] };
|
|
}
|
|
|
|
const errors = [];
|
|
|
|
// Validate type
|
|
if (!['ship', 'material', 'consumable', 'cosmetic', 'key', 'container'].includes(item.type)) {
|
|
errors.push(`Invalid item type: ${item.type}`);
|
|
}
|
|
|
|
// Validate rarity
|
|
if (!['common', 'uncommon', 'rare', 'legendary'].includes(item.rarity)) {
|
|
errors.push(`Invalid rarity: ${item.rarity}`);
|
|
}
|
|
|
|
// Validate stackable items
|
|
if (item.stackable && (!item.maxStack || item.maxStack < 1)) {
|
|
errors.push('Stackable items must have maxStack >= 1');
|
|
}
|
|
|
|
return { valid: errors.length === 0, errors };
|
|
}
|
|
|
|
/**
|
|
* Set server URL dynamically
|
|
*/
|
|
setServerUrl(url) {
|
|
this.serverUrl = url;
|
|
console.log(`[ITEM SYSTEM] Server URL set to: ${this.serverUrl}`);
|
|
}
|
|
|
|
/**
|
|
* Process item to add full texture URL
|
|
*/
|
|
processItem(item) {
|
|
const processedItem = { ...item };
|
|
|
|
// Add full texture URL if texturePath exists
|
|
if (item.texturePath) {
|
|
processedItem.texture = `${this.serverUrl}/${item.texturePath}`;
|
|
}
|
|
|
|
// Fallback to old texture field if exists
|
|
else if (item.texture) {
|
|
processedItem.texture = item.texture;
|
|
}
|
|
|
|
// Default placeholder
|
|
else {
|
|
processedItem.texture = `${this.serverUrl}/images/ui/placeholder.png`;
|
|
}
|
|
|
|
return processedItem;
|
|
}
|
|
|
|
/**
|
|
* Get items with processed URLs
|
|
*/
|
|
getItemsByType(type) {
|
|
const items = this.itemCatalog[type] || [];
|
|
return items.map(item => this.processItem(item));
|
|
}
|
|
|
|
/**
|
|
* Get all items with processed URLs
|
|
*/
|
|
getAllItems() {
|
|
const allItems = {};
|
|
|
|
Object.keys(this.itemCatalog).forEach(type => {
|
|
allItems[type] = this.getItemsByType(type);
|
|
});
|
|
|
|
return allItems;
|
|
}
|
|
|
|
/**
|
|
* Get random shop items with processed URLs
|
|
*/
|
|
getRandomShopItems(count = 8) {
|
|
const allItems = Object.values(this.itemCatalog).flat();
|
|
const selectedItems = [];
|
|
|
|
// Randomly select items
|
|
const shuffled = [...allItems].sort(() => Math.random() - 0.5);
|
|
|
|
for (let i = 0; i < Math.min(count, shuffled.length); i++) {
|
|
selectedItems.push(this.processItem(shuffled[i]));
|
|
}
|
|
|
|
return selectedItems;
|
|
}
|
|
}
|
|
|
|
module.exports = ItemSystem;
|