diff --git a/src/assets/icons/items/item_advanced_toolkit.png b/src/assets/icons/items/item_advanced_toolkit.png new file mode 100644 index 0000000..5892730 Binary files /dev/null and b/src/assets/icons/items/item_advanced_toolkit.png differ diff --git a/src/assets/icons/items/item_amulet_vitality.png b/src/assets/icons/items/item_amulet_vitality.png new file mode 100644 index 0000000..0d99b36 Binary files /dev/null and b/src/assets/icons/items/item_amulet_vitality.png differ diff --git a/src/assets/icons/items/item_apprentice_wand.png b/src/assets/icons/items/item_apprentice_wand.png new file mode 100644 index 0000000..47613e8 Binary files /dev/null and b/src/assets/icons/items/item_apprentice_wand.png differ diff --git a/src/assets/icons/items/item_crystal_staff.png b/src/assets/icons/items/item_crystal_staff.png new file mode 100644 index 0000000..b582784 Binary files /dev/null and b/src/assets/icons/items/item_crystal_staff.png differ diff --git a/src/assets/icons/items/item_dagger.png b/src/assets/icons/items/item_dagger.png new file mode 100644 index 0000000..b52d854 Binary files /dev/null and b/src/assets/icons/items/item_dagger.png differ diff --git a/src/assets/icons/items/item_leaf_robes.png b/src/assets/icons/items/item_leaf_robes.png new file mode 100644 index 0000000..69b4a62 Binary files /dev/null and b/src/assets/icons/items/item_leaf_robes.png differ diff --git a/src/assets/icons/items/item_leather_apron.png b/src/assets/icons/items/item_leather_apron.png new file mode 100644 index 0000000..745544b Binary files /dev/null and b/src/assets/icons/items/item_leather_apron.png differ diff --git a/src/assets/icons/items/item_lockpick_set.png b/src/assets/icons/items/item_lockpick_set.png new file mode 100644 index 0000000..251d915 Binary files /dev/null and b/src/assets/icons/items/item_lockpick_set.png differ diff --git a/src/assets/icons/items/item_padded_vest.png b/src/assets/icons/items/item_padded_vest.png new file mode 100644 index 0000000..412054a Binary files /dev/null and b/src/assets/icons/items/item_padded_vest.png differ diff --git a/src/assets/icons/items/item_reinforced_plate.png b/src/assets/icons/items/item_reinforced_plate.png new file mode 100644 index 0000000..5f0149d Binary files /dev/null and b/src/assets/icons/items/item_reinforced_plate.png differ diff --git a/src/assets/icons/items/item_robes.png b/src/assets/icons/items/item_robes.png new file mode 100644 index 0000000..6b31154 Binary files /dev/null and b/src/assets/icons/items/item_robes.png differ diff --git a/src/assets/icons/items/item_rusty_blade.png b/src/assets/icons/items/item_rusty_blade.png new file mode 100644 index 0000000..19fe080 Binary files /dev/null and b/src/assets/icons/items/item_rusty_blade.png differ diff --git a/src/assets/icons/items/item_scrap_plate.png b/src/assets/icons/items/item_scrap_plate.png new file mode 100644 index 0000000..dc3f989 Binary files /dev/null and b/src/assets/icons/items/item_scrap_plate.png differ diff --git a/src/assets/icons/items/item_serrated_dagger.png b/src/assets/icons/items/item_serrated_dagger.png new file mode 100644 index 0000000..fe47341 Binary files /dev/null and b/src/assets/icons/items/item_serrated_dagger.png differ diff --git a/src/assets/icons/items/item_silk_weave_robes.png b/src/assets/icons/items/item_silk_weave_robes.png new file mode 100644 index 0000000..37525b2 Binary files /dev/null and b/src/assets/icons/items/item_silk_weave_robes.png differ diff --git a/src/assets/icons/items/item_staff.png b/src/assets/icons/items/item_staff.png new file mode 100644 index 0000000..0283159 Binary files /dev/null and b/src/assets/icons/items/item_staff.png differ diff --git a/src/assets/icons/items/item_steel_longsword.png b/src/assets/icons/items/item_steel_longsword.png new file mode 100644 index 0000000..7e6d696 Binary files /dev/null and b/src/assets/icons/items/item_steel_longsword.png differ diff --git a/src/assets/icons/items/item_turret_kit.png b/src/assets/icons/items/item_turret_kit.png new file mode 100644 index 0000000..8412318 Binary files /dev/null and b/src/assets/icons/items/item_turret_kit.png differ diff --git a/src/assets/icons/items/item_wrench.png b/src/assets/icons/items/item_wrench.png new file mode 100644 index 0000000..a7a94d4 Binary files /dev/null and b/src/assets/icons/items/item_wrench.png differ diff --git a/src/assets/icons/mission_bomb.png b/src/assets/icons/mission_bomb.png new file mode 100644 index 0000000..ab8658d Binary files /dev/null and b/src/assets/icons/mission_bomb.png differ diff --git a/src/assets/icons/mission_boss_final.png b/src/assets/icons/mission_boss_final.png new file mode 100644 index 0000000..1deddb0 Binary files /dev/null and b/src/assets/icons/mission_boss_final.png differ diff --git a/src/assets/icons/mission_boss_gate.png b/src/assets/icons/mission_boss_gate.png new file mode 100644 index 0000000..7ae9ca2 Binary files /dev/null and b/src/assets/icons/mission_boss_gate.png differ diff --git a/src/assets/icons/mission_boss_plant.png b/src/assets/icons/mission_boss_plant.png new file mode 100644 index 0000000..624fd5e Binary files /dev/null and b/src/assets/icons/mission_boss_plant.png differ diff --git a/src/assets/icons/mission_climb.png b/src/assets/icons/mission_climb.png new file mode 100644 index 0000000..7c8fbf5 Binary files /dev/null and b/src/assets/icons/mission_climb.png differ diff --git a/src/assets/icons/mission_coin.png b/src/assets/icons/mission_coin.png new file mode 100644 index 0000000..12cb819 Binary files /dev/null and b/src/assets/icons/mission_coin.png differ diff --git a/src/assets/icons/mission_core.png b/src/assets/icons/mission_core.png new file mode 100644 index 0000000..18613ac Binary files /dev/null and b/src/assets/icons/mission_core.png differ diff --git a/src/assets/icons/mission_diplomat.png b/src/assets/icons/mission_diplomat.png new file mode 100644 index 0000000..65f3126 Binary files /dev/null and b/src/assets/icons/mission_diplomat.png differ diff --git a/src/assets/icons/mission_elevator.png b/src/assets/icons/mission_elevator.png new file mode 100644 index 0000000..63829bc Binary files /dev/null and b/src/assets/icons/mission_elevator.png differ diff --git a/src/assets/icons/mission_escort.png b/src/assets/icons/mission_escort.png new file mode 100644 index 0000000..8b03f3f Binary files /dev/null and b/src/assets/icons/mission_escort.png differ diff --git a/src/assets/icons/mission_flag.png b/src/assets/icons/mission_flag.png new file mode 100644 index 0000000..5290229 Binary files /dev/null and b/src/assets/icons/mission_flag.png differ diff --git a/src/assets/icons/mission_leaf.png b/src/assets/icons/mission_leaf.png new file mode 100644 index 0000000..894c07d Binary files /dev/null and b/src/assets/icons/mission_leaf.png differ diff --git a/src/assets/icons/mission_run.png b/src/assets/icons/mission_run.png new file mode 100644 index 0000000..46e69de Binary files /dev/null and b/src/assets/icons/mission_run.png differ diff --git a/src/assets/icons/mission_search.png b/src/assets/icons/mission_search.png new file mode 100644 index 0000000..ee13971 Binary files /dev/null and b/src/assets/icons/mission_search.png differ diff --git a/src/assets/icons/mission_shadow.png b/src/assets/icons/mission_shadow.png new file mode 100644 index 0000000..7af9452 Binary files /dev/null and b/src/assets/icons/mission_shadow.png differ diff --git a/src/assets/icons/mission_shield.png b/src/assets/icons/mission_shield.png new file mode 100644 index 0000000..8e87933 Binary files /dev/null and b/src/assets/icons/mission_shield.png differ diff --git a/src/assets/icons/mission_skull.png b/src/assets/icons/mission_skull.png new file mode 100644 index 0000000..af136d4 Binary files /dev/null and b/src/assets/icons/mission_skull.png differ diff --git a/src/assets/icons/mission_skull_poison.png b/src/assets/icons/mission_skull_poison.png new file mode 100644 index 0000000..c4af014 Binary files /dev/null and b/src/assets/icons/mission_skull_poison.png differ diff --git a/src/assets/icons/mission_target.png b/src/assets/icons/mission_target.png new file mode 100644 index 0000000..e3d8f8a Binary files /dev/null and b/src/assets/icons/mission_target.png differ diff --git a/src/assets/icons/mission_vip.png b/src/assets/icons/mission_vip.png new file mode 100644 index 0000000..486ad13 Binary files /dev/null and b/src/assets/icons/mission_vip.png differ diff --git a/src/items/Item.js b/src/items/Item.js index d2c10f6..1f5f009 100644 --- a/src/items/Item.js +++ b/src/items/Item.js @@ -11,7 +11,10 @@ export class Item { this.name = def.name; this.type = def.type; // WEAPON, ARMOR, UTILITY, RELIC this.rarity = def.rarity || "COMMON"; + this.rarity = def.rarity || "COMMON"; this.tags = def.tags || []; + this.icon = def.icon || null; + this.description = def.description || ""; // Base Stats (e.g. { attack: 5, defense: 2 }) this.stats = def.stats || {}; diff --git a/src/items/tier2_gear.json b/src/items/tier2_gear.json new file mode 100644 index 0000000..ddf6834 --- /dev/null +++ b/src/items/tier2_gear.json @@ -0,0 +1,103 @@ +[ + { + "id": "ITEM_STEEL_LONGSWORD", + "name": "Steel Longsword", + "type": "WEAPON", + "rarity": "UNCOMMON", + "tags": ["PHYSICAL", "MELEE"], + "stats": { + "attack": 6, + "accuracy": 5 + }, + "description": "A finely forged blade, balanced and sharp.", + "icon": "assets/icons/items/item_steel_longsword.png" + }, + { + "id": "ITEM_REINFORCED_PLATE", + "name": "Reinforced Plate", + "type": "ARMOR", + "rarity": "UNCOMMON", + "tags": ["HEAVY"], + "stats": { + "defense": 6, + "health": 10, + "speed": -2 + }, + "description": "Heavy steel plates reinforced with chainmail.", + "icon": "assets/icons/items/item_reinforced_plate.png" + }, + { + "id": "ITEM_CRYSTAL_STAFF", + "name": "Crystal Staff", + "type": "WEAPON", + "rarity": "UNCOMMON", + "tags": ["MAGIC", "RANGED", "TWO_HANDED"], + "stats": { + "magic": 8, + "willpower": 3 + }, + "description": "Embedded with a large mana crystal for focusing power.", + "icon": "assets/icons/items/item_crystal_staff.png" + }, + { + "id": "ITEM_SILK_WEAVE_ROBES", + "name": "Silk Weave Robes", + "type": "ARMOR", + "rarity": "UNCOMMON", + "tags": ["LIGHT", "MAGIC"], + "stats": { + "willpower": 6, + "magic": 3, + "speed": 1 + }, + "description": "Enchanted silk that offers protection without weight.", + "icon": "assets/icons/items/item_silk_weave_robes.png" + }, + { + "id": "ITEM_SERRATED_DAGGER", + "name": "Serrated Dagger", + "type": "WEAPON", + "rarity": "UNCOMMON", + "tags": ["PHYSICAL", "MELEE", "LIGHT"], + "stats": { + "attack": 5, + "crit_chance": 10, + "speed": 2 + }, + "description": "Designed to inflict bleeding wounds.", + "icon": "assets/icons/items/item_serrated_dagger.png" + }, + { + "id": "ITEM_AMULET_VITALITY", + "name": "Amulet of Vitality", + "type": "ACCESSORY", + "rarity": "RARE", + "tags": ["MAGIC"], + "stats": { + "health": 20, + "regen": 2 + }, + "description": "Radiates a warm, life-giving energy.", + "icon": "assets/icons/items/item_amulet_vitality.png" + }, + { + "id": "ITEM_ADVANCED_TOOLKIT", + "name": "Advanced Toolkit", + "type": "UTILITY", + "rarity": "UNCOMMON", + "tags": ["TECH"], + "stats": { + "tech": 5 + }, + "passive_effects": [ + { + "trigger": "ON_INTERACT", + "condition": "mechanical", + "action": "REPAIR", + "value": 10 + } + ], + "description": "Contains precision tools for complex machinery.", + "icon": "assets/icons/items/item_advanced_toolkit.png" + } +] diff --git a/src/managers/ItemRegistry.js b/src/managers/ItemRegistry.js index e42eaed..c39c436 100644 --- a/src/managers/ItemRegistry.js +++ b/src/managers/ItemRegistry.js @@ -35,9 +35,14 @@ export class ItemRegistry { * @returns {Promise} */ async _doLoadAll() { - // Lazy-load tier1_gear.json - const tier1Gear = await import("../items/tier1_gear.json", { with: { type: "json" } }).then(m => m.default); - + // Lazy-load item definitions + const tier1Gear = await import("../items/tier1_gear.json", { + with: { type: "json" }, + }).then((m) => m.default); + const tier2Gear = await import("../items/tier2_gear.json", { + with: { type: "json" }, + }).then((m) => m.default); + // Load tier1_gear.json for (const itemDef of tier1Gear) { if (itemDef && itemDef.id) { @@ -46,6 +51,14 @@ export class ItemRegistry { } } + // Load tier2_gear.json + for (const itemDef of tier2Gear) { + if (itemDef && itemDef.id) { + const item = new Item(itemDef); + this.items.set(itemDef.id, item); + } + } + console.log(`Loaded ${this.items.size} items`); } @@ -69,4 +82,3 @@ export class ItemRegistry { // Export singleton instance export const itemRegistry = new ItemRegistry(); - diff --git a/src/managers/MarketManager.js b/src/managers/MarketManager.js index 0c9d3f5..c8e0d7c 100644 --- a/src/managers/MarketManager.js +++ b/src/managers/MarketManager.js @@ -78,7 +78,6 @@ export class MarketManager { this.marketState = { generationId: `INIT_${Date.now()}`, stock: [], - buyback: [], }; await this.generateStock(1); } @@ -130,20 +129,26 @@ export class MarketManager { const newStock = []; if (tier === 1) { - // Tier 1: Smith (5 Common Weapons, 3 Common Armor) - const smithWeapons = this._generateMerchantStock( + // Tier 1: Weapons, Armor, and basic Utility + const weapons = this._generateMerchantStock( allItems, ["WEAPON"], { COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 }, 5 ); - const smithArmor = this._generateMerchantStock( + const armor = this._generateMerchantStock( allItems, ["ARMOR"], { COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 }, 3 ); - newStock.push(...smithWeapons, ...smithArmor); + const utility = this._generateMerchantStock( + allItems, + ["UTILITY"], + { COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 }, + 2 + ); + newStock.push(...weapons, ...armor, ...utility); // Alchemist (5 Potions, 2 Grenades) - simplified for now since we don't have potions in tier1_gear // Will add when consumables are available @@ -182,8 +187,7 @@ export class MarketManager { const stockWithIds = newStock.map((item, index) => { const itemDef = this.itemRegistry.get(item.defId); const basePrice = this._calculateBasePrice(itemDef); - const variance = 1 + (Math.random() * 0.2 - 0.1); // ±10% variance - const price = Math.floor(basePrice * variance); + const price = basePrice; return { id: `STOCK_${Date.now()}_${index}`, @@ -295,10 +299,9 @@ export class MarketManager { */ async buyItem(stockId) { // Check both stock and buyback - let marketItem = this.marketState.stock.find((item) => item.id === stockId); - if (!marketItem) { - marketItem = this.marketState.buyback.find((item) => item.id === stockId); - } + const marketItem = this.marketState.stock.find( + (item) => item.id === stockId + ); if (!marketItem || marketItem.purchased) { return false; } @@ -402,23 +405,21 @@ export class MarketManager { }) ); - // 6. Create buyback entry (limit 10) - if (this.marketState.buyback.length >= 10) { - this.marketState.buyback.shift(); // Remove oldest - } - - const buybackItem = { - id: `BUYBACK_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + // 6. Add to Market Stock (at full price) + const marketItem = { + id: `STOCK_${Date.now()}_SOLD_${Math.random() + .toString(36) + .substr(2, 5)}`, defId: itemInstance.defId, type: itemDef.type, rarity: itemDef.rarity, - price: sellPrice, // Buyback price = sell price + price: basePrice, // Resell at full market value discount: 0, purchased: false, instanceData: { ...itemInstance }, // Store copy of original instance }; - this.marketState.buyback.push(buybackItem); + this.marketState.stock.push(marketItem); // 7. Save market state await this.persistence.saveMarketState(this.marketState); @@ -445,15 +446,16 @@ export class MarketManager { */ getStockForMerchant(merchantType) { if (merchantType === "BUYBACK") { - return this.marketState.buyback; + return []; } // Filter stock by merchant type const typeMap = { - SMITH: ["WEAPON", "ARMOR"], - TAILOR: ["ARMOR"], - ALCHEMIST: ["CONSUMABLE", "UTILITY"], - SCAVENGER: ["RELIC", "UTILITY"], + WEAPONS: ["WEAPON"], + ARMOR: ["ARMOR"], + ENGINEER: ["UTILITY"], + ALCHEMIST: ["CONSUMABLE"], + BAZAAR: ["RELIC"], }; const allowedTypes = typeMap[merchantType] || []; @@ -462,6 +464,39 @@ export class MarketManager { ); } + /** + * Gets sellable items from player's hub stash. + * @returns {MarketItem[]} + */ + getSellableInventory() { + if (!this.inventoryManager?.hubStash) return []; + + // Get all items from stash + const items = this.inventoryManager.hubStash.items || []; + + // Format as MarketItems + return items + .map((itemInstance) => { + const itemDef = this.itemRegistry.get(itemInstance.defId); + if (!itemDef) return null; + + const basePrice = this._calculateBasePrice(itemDef); + const sellPrice = Math.floor(basePrice * 0.25); + + return { + id: itemInstance.uid, // Use UID for selling + defId: itemInstance.defId, + type: itemDef.type, + rarity: itemDef.rarity, + price: sellPrice, + discount: 0, + purchased: false, // Not relevant for selling, but keeps shape + isSellable: true, // Flag for UI + }; + }) + .filter((item) => item !== null); + } + /** * Cleanup - remove event listeners. */ diff --git a/src/ui/screens/marketplace-screen.js b/src/ui/screens/marketplace-screen.js index ed9f71e..204f9d8 100644 --- a/src/ui/screens/marketplace-screen.js +++ b/src/ui/screens/marketplace-screen.js @@ -207,6 +207,13 @@ export class MarketplaceScreen extends LitElement { color: var(--color-text-primary); } + .item-icon { + width: 80px; + height: 80px; + object-fit: contain; + margin-bottom: var(--spacing-sm); + } + .item-type { font-size: var(--font-size-xs); color: var(--color-text-secondary); @@ -332,7 +339,7 @@ export class MarketplaceScreen extends LitElement { super(); this.marketManager = null; this.wallet = { aetherShards: 0, ancientCores: 0 }; - this.activeMerchant = "SMITH"; + this.activeMerchant = "WEAPONS"; this.activeFilter = "ALL"; this.selectedItem = null; this.showModal = false; @@ -366,13 +373,48 @@ export class MarketplaceScreen extends LitElement { _getStock() { if (!this.marketManager) return []; - const stock = this.marketManager.getStockForMerchant(this.activeMerchant); - // Apply filter - if (this.activeFilter === "ALL") { - return stock; + let stock = []; + if (this.activeMerchant === "SELL") { + stock = this.marketManager.getSellableInventory(); + } else { + stock = this.marketManager.getStockForMerchant(this.activeMerchant); } - return stock.filter((item) => item.type === this.activeFilter); + + // Filter out purchased items and apply type filter + let filteredStock = stock.filter((item) => !item.purchased); + + if (this.activeFilter !== "ALL") { + filteredStock = filteredStock.filter( + (item) => item.type === this.activeFilter + ); + } + + // Sort: Type -> Rarity -> Name -> Price + const rarityWeight = { + ANCIENT: 4, + RARE: 3, + UNCOMMON: 2, + COMMON: 1, + }; + + return filteredStock.sort((a, b) => { + // 1. Type + if (a.type !== b.type) return a.type.localeCompare(b.type); + + // 2. Rarity (High to Low) + const rarityA = rarityWeight[a.rarity] || 0; + const rarityB = rarityWeight[b.rarity] || 0; + if (rarityA !== rarityB) return rarityB - rarityA; + + // 3. Name + const nameA = this._getItemName(a); + const nameB = this._getItemName(b); + if (nameA !== nameB) return nameA.localeCompare(nameB); + + // 4. Price + return a.price - b.price; + }); } _onMerchantClick(merchant) { @@ -402,6 +444,11 @@ export class MarketplaceScreen extends LitElement { async _confirmBuy() { if (!this.selectedItem || !this.marketManager) return; + if (this.activeMerchant === "SELL") { + await this._confirmSell(); + return; + } + const success = await this.marketManager.buyItem(this.selectedItem.id); if (success) { this._updateWallet(); @@ -412,6 +459,19 @@ export class MarketplaceScreen extends LitElement { } } + async _confirmSell() { + if (!this.selectedItem || !this.marketManager) return; + + const success = await this.marketManager.sellItem(this.selectedItem.id); + if (success) { + this._updateWallet(); + this._closeModal(); + this.requestUpdate(); + } else { + alert("Sell failed."); + } + } + _canAfford(item) { return this.wallet.aetherShards >= item.price; } @@ -429,6 +489,30 @@ export class MarketplaceScreen extends LitElement { return item.defId; } + _getItemIcon(item) { + if (this.marketManager?.itemRegistry) { + const itemDef = this.marketManager.itemRegistry.get(item.defId); + return itemDef?.icon || null; + } + return null; + } + + _getItemDescription(item) { + if (this.marketManager?.itemRegistry) { + const itemDef = this.marketManager.itemRegistry.get(item.defId); + return itemDef?.description || "No description available."; + } + return ""; + } + + _getItemStats(item) { + if (this.marketManager?.itemRegistry) { + const itemDef = this.marketManager.itemRegistry.get(item.defId); + return itemDef?.stats || {}; + } + return {}; + } + _dispatchClose() { this.dispatchEvent( new CustomEvent("market-closed", { @@ -441,10 +525,12 @@ export class MarketplaceScreen extends LitElement { render() { const stock = this._getStock(); const merchants = [ - { id: "SMITH", icon: "⚔️", label: "Smith" }, - { id: "TAILOR", icon: "🧥", label: "Tailor" }, + { id: "WEAPONS", icon: "⚔️", label: "Weapons" }, + { id: "ARMOR", icon: "🛡️", label: "Armor" }, + { id: "ENGINEER", icon: "🔧", label: "Engineer" }, { id: "ALCHEMIST", icon: "⚗️", label: "Alchemist" }, - { id: "BUYBACK", icon: "♻️", label: "Buyback" }, + { id: "BAZAAR", icon: "🏺", label: "Bazaar" }, + { id: "SELL", icon: "💰", label: "Sell Items" }, ]; const filters = [ @@ -517,6 +603,13 @@ export class MarketplaceScreen extends LitElement { ${item.purchased ? html`
SOLD
` : ""} + ${this._getItemIcon(item) + ? html`${this._getItemName(item)}` + : html`
📦
`}
${this._getItemName(item)}
${item.type}
${item.price} 💎
@@ -535,7 +628,11 @@ export class MarketplaceScreen extends LitElement { > - ${!this._canAfford(this.selectedItem) + ${this.activeMerchant !== "SELL" && + !this._canAfford(this.selectedItem) ? html`
Insufficient funds!
` @@ -559,10 +682,13 @@ export class MarketplaceScreen extends LitElement {