feat: Add Tier 2 items, icons, and market support
BIN
src/assets/icons/items/item_advanced_toolkit.png
Normal file
|
After Width: | Height: | Size: 903 KiB |
BIN
src/assets/icons/items/item_amulet_vitality.png
Normal file
|
After Width: | Height: | Size: 771 KiB |
BIN
src/assets/icons/items/item_apprentice_wand.png
Normal file
|
After Width: | Height: | Size: 446 KiB |
BIN
src/assets/icons/items/item_crystal_staff.png
Normal file
|
After Width: | Height: | Size: 707 KiB |
BIN
src/assets/icons/items/item_dagger.png
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
src/assets/icons/items/item_leaf_robes.png
Normal file
|
After Width: | Height: | Size: 629 KiB |
BIN
src/assets/icons/items/item_leather_apron.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
src/assets/icons/items/item_lockpick_set.png
Normal file
|
After Width: | Height: | Size: 774 KiB |
BIN
src/assets/icons/items/item_padded_vest.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
src/assets/icons/items/item_reinforced_plate.png
Normal file
|
After Width: | Height: | Size: 742 KiB |
BIN
src/assets/icons/items/item_robes.png
Normal file
|
After Width: | Height: | Size: 660 KiB |
BIN
src/assets/icons/items/item_rusty_blade.png
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
src/assets/icons/items/item_scrap_plate.png
Normal file
|
After Width: | Height: | Size: 590 KiB |
BIN
src/assets/icons/items/item_serrated_dagger.png
Normal file
|
After Width: | Height: | Size: 697 KiB |
BIN
src/assets/icons/items/item_silk_weave_robes.png
Normal file
|
After Width: | Height: | Size: 751 KiB |
BIN
src/assets/icons/items/item_staff.png
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
src/assets/icons/items/item_steel_longsword.png
Normal file
|
After Width: | Height: | Size: 768 KiB |
BIN
src/assets/icons/items/item_turret_kit.png
Normal file
|
After Width: | Height: | Size: 817 KiB |
BIN
src/assets/icons/items/item_wrench.png
Normal file
|
After Width: | Height: | Size: 495 KiB |
BIN
src/assets/icons/mission_bomb.png
Normal file
|
After Width: | Height: | Size: 590 KiB |
BIN
src/assets/icons/mission_boss_final.png
Normal file
|
After Width: | Height: | Size: 627 KiB |
BIN
src/assets/icons/mission_boss_gate.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
BIN
src/assets/icons/mission_boss_plant.png
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
src/assets/icons/mission_climb.png
Normal file
|
After Width: | Height: | Size: 699 KiB |
BIN
src/assets/icons/mission_coin.png
Normal file
|
After Width: | Height: | Size: 678 KiB |
BIN
src/assets/icons/mission_core.png
Normal file
|
After Width: | Height: | Size: 601 KiB |
BIN
src/assets/icons/mission_diplomat.png
Normal file
|
After Width: | Height: | Size: 596 KiB |
BIN
src/assets/icons/mission_elevator.png
Normal file
|
After Width: | Height: | Size: 597 KiB |
BIN
src/assets/icons/mission_escort.png
Normal file
|
After Width: | Height: | Size: 587 KiB |
BIN
src/assets/icons/mission_flag.png
Normal file
|
After Width: | Height: | Size: 568 KiB |
BIN
src/assets/icons/mission_leaf.png
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
src/assets/icons/mission_run.png
Normal file
|
After Width: | Height: | Size: 576 KiB |
BIN
src/assets/icons/mission_search.png
Normal file
|
After Width: | Height: | Size: 578 KiB |
BIN
src/assets/icons/mission_shadow.png
Normal file
|
After Width: | Height: | Size: 557 KiB |
BIN
src/assets/icons/mission_shield.png
Normal file
|
After Width: | Height: | Size: 725 KiB |
BIN
src/assets/icons/mission_skull.png
Normal file
|
After Width: | Height: | Size: 648 KiB |
BIN
src/assets/icons/mission_skull_poison.png
Normal file
|
After Width: | Height: | Size: 763 KiB |
BIN
src/assets/icons/mission_target.png
Normal file
|
After Width: | Height: | Size: 661 KiB |
BIN
src/assets/icons/mission_vip.png
Normal file
|
After Width: | Height: | Size: 594 KiB |
|
|
@ -11,7 +11,10 @@ export class Item {
|
||||||
this.name = def.name;
|
this.name = def.name;
|
||||||
this.type = def.type; // WEAPON, ARMOR, UTILITY, RELIC
|
this.type = def.type; // WEAPON, ARMOR, UTILITY, RELIC
|
||||||
this.rarity = def.rarity || "COMMON";
|
this.rarity = def.rarity || "COMMON";
|
||||||
|
this.rarity = def.rarity || "COMMON";
|
||||||
this.tags = def.tags || [];
|
this.tags = def.tags || [];
|
||||||
|
this.icon = def.icon || null;
|
||||||
|
this.description = def.description || "";
|
||||||
|
|
||||||
// Base Stats (e.g. { attack: 5, defense: 2 })
|
// Base Stats (e.g. { attack: 5, defense: 2 })
|
||||||
this.stats = def.stats || {};
|
this.stats = def.stats || {};
|
||||||
|
|
|
||||||
103
src/items/tier2_gear.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -35,8 +35,13 @@ export class ItemRegistry {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async _doLoadAll() {
|
async _doLoadAll() {
|
||||||
// Lazy-load tier1_gear.json
|
// Lazy-load item definitions
|
||||||
const tier1Gear = await import("../items/tier1_gear.json", { with: { type: "json" } }).then(m => m.default);
|
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
|
// Load tier1_gear.json
|
||||||
for (const itemDef of tier1Gear) {
|
for (const itemDef of tier1Gear) {
|
||||||
|
|
@ -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`);
|
console.log(`Loaded ${this.items.size} items`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,4 +82,3 @@ export class ItemRegistry {
|
||||||
|
|
||||||
// Export singleton instance
|
// Export singleton instance
|
||||||
export const itemRegistry = new ItemRegistry();
|
export const itemRegistry = new ItemRegistry();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,6 @@ export class MarketManager {
|
||||||
this.marketState = {
|
this.marketState = {
|
||||||
generationId: `INIT_${Date.now()}`,
|
generationId: `INIT_${Date.now()}`,
|
||||||
stock: [],
|
stock: [],
|
||||||
buyback: [],
|
|
||||||
};
|
};
|
||||||
await this.generateStock(1);
|
await this.generateStock(1);
|
||||||
}
|
}
|
||||||
|
|
@ -130,20 +129,26 @@ export class MarketManager {
|
||||||
const newStock = [];
|
const newStock = [];
|
||||||
|
|
||||||
if (tier === 1) {
|
if (tier === 1) {
|
||||||
// Tier 1: Smith (5 Common Weapons, 3 Common Armor)
|
// Tier 1: Weapons, Armor, and basic Utility
|
||||||
const smithWeapons = this._generateMerchantStock(
|
const weapons = this._generateMerchantStock(
|
||||||
allItems,
|
allItems,
|
||||||
["WEAPON"],
|
["WEAPON"],
|
||||||
{ COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 },
|
{ COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 },
|
||||||
5
|
5
|
||||||
);
|
);
|
||||||
const smithArmor = this._generateMerchantStock(
|
const armor = this._generateMerchantStock(
|
||||||
allItems,
|
allItems,
|
||||||
["ARMOR"],
|
["ARMOR"],
|
||||||
{ COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 },
|
{ COMMON: 1, UNCOMMON: 0, RARE: 0, ANCIENT: 0 },
|
||||||
3
|
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
|
// Alchemist (5 Potions, 2 Grenades) - simplified for now since we don't have potions in tier1_gear
|
||||||
// Will add when consumables are available
|
// Will add when consumables are available
|
||||||
|
|
@ -182,8 +187,7 @@ export class MarketManager {
|
||||||
const stockWithIds = newStock.map((item, index) => {
|
const stockWithIds = newStock.map((item, index) => {
|
||||||
const itemDef = this.itemRegistry.get(item.defId);
|
const itemDef = this.itemRegistry.get(item.defId);
|
||||||
const basePrice = this._calculateBasePrice(itemDef);
|
const basePrice = this._calculateBasePrice(itemDef);
|
||||||
const variance = 1 + (Math.random() * 0.2 - 0.1); // ±10% variance
|
const price = basePrice;
|
||||||
const price = Math.floor(basePrice * variance);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `STOCK_${Date.now()}_${index}`,
|
id: `STOCK_${Date.now()}_${index}`,
|
||||||
|
|
@ -295,10 +299,9 @@ export class MarketManager {
|
||||||
*/
|
*/
|
||||||
async buyItem(stockId) {
|
async buyItem(stockId) {
|
||||||
// Check both stock and buyback
|
// Check both stock and buyback
|
||||||
let marketItem = this.marketState.stock.find((item) => item.id === stockId);
|
const marketItem = this.marketState.stock.find(
|
||||||
if (!marketItem) {
|
(item) => item.id === stockId
|
||||||
marketItem = this.marketState.buyback.find((item) => item.id === stockId);
|
);
|
||||||
}
|
|
||||||
if (!marketItem || marketItem.purchased) {
|
if (!marketItem || marketItem.purchased) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -402,23 +405,21 @@ export class MarketManager {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// 6. Create buyback entry (limit 10)
|
// 6. Add to Market Stock (at full price)
|
||||||
if (this.marketState.buyback.length >= 10) {
|
const marketItem = {
|
||||||
this.marketState.buyback.shift(); // Remove oldest
|
id: `STOCK_${Date.now()}_SOLD_${Math.random()
|
||||||
}
|
.toString(36)
|
||||||
|
.substr(2, 5)}`,
|
||||||
const buybackItem = {
|
|
||||||
id: `BUYBACK_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
||||||
defId: itemInstance.defId,
|
defId: itemInstance.defId,
|
||||||
type: itemDef.type,
|
type: itemDef.type,
|
||||||
rarity: itemDef.rarity,
|
rarity: itemDef.rarity,
|
||||||
price: sellPrice, // Buyback price = sell price
|
price: basePrice, // Resell at full market value
|
||||||
discount: 0,
|
discount: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
instanceData: { ...itemInstance }, // Store copy of original instance
|
instanceData: { ...itemInstance }, // Store copy of original instance
|
||||||
};
|
};
|
||||||
|
|
||||||
this.marketState.buyback.push(buybackItem);
|
this.marketState.stock.push(marketItem);
|
||||||
|
|
||||||
// 7. Save market state
|
// 7. Save market state
|
||||||
await this.persistence.saveMarketState(this.marketState);
|
await this.persistence.saveMarketState(this.marketState);
|
||||||
|
|
@ -445,15 +446,16 @@ export class MarketManager {
|
||||||
*/
|
*/
|
||||||
getStockForMerchant(merchantType) {
|
getStockForMerchant(merchantType) {
|
||||||
if (merchantType === "BUYBACK") {
|
if (merchantType === "BUYBACK") {
|
||||||
return this.marketState.buyback;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter stock by merchant type
|
// Filter stock by merchant type
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
SMITH: ["WEAPON", "ARMOR"],
|
WEAPONS: ["WEAPON"],
|
||||||
TAILOR: ["ARMOR"],
|
ARMOR: ["ARMOR"],
|
||||||
ALCHEMIST: ["CONSUMABLE", "UTILITY"],
|
ENGINEER: ["UTILITY"],
|
||||||
SCAVENGER: ["RELIC", "UTILITY"],
|
ALCHEMIST: ["CONSUMABLE"],
|
||||||
|
BAZAAR: ["RELIC"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const allowedTypes = typeMap[merchantType] || [];
|
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.
|
* Cleanup - remove event listeners.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,13 @@ export class MarketplaceScreen extends LitElement {
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.item-type {
|
.item-type {
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
|
@ -332,7 +339,7 @@ export class MarketplaceScreen extends LitElement {
|
||||||
super();
|
super();
|
||||||
this.marketManager = null;
|
this.marketManager = null;
|
||||||
this.wallet = { aetherShards: 0, ancientCores: 0 };
|
this.wallet = { aetherShards: 0, ancientCores: 0 };
|
||||||
this.activeMerchant = "SMITH";
|
this.activeMerchant = "WEAPONS";
|
||||||
this.activeFilter = "ALL";
|
this.activeFilter = "ALL";
|
||||||
this.selectedItem = null;
|
this.selectedItem = null;
|
||||||
this.showModal = false;
|
this.showModal = false;
|
||||||
|
|
@ -366,13 +373,48 @@ export class MarketplaceScreen extends LitElement {
|
||||||
|
|
||||||
_getStock() {
|
_getStock() {
|
||||||
if (!this.marketManager) return [];
|
if (!this.marketManager) return [];
|
||||||
const stock = this.marketManager.getStockForMerchant(this.activeMerchant);
|
|
||||||
|
|
||||||
// Apply filter
|
let stock = [];
|
||||||
if (this.activeFilter === "ALL") {
|
if (this.activeMerchant === "SELL") {
|
||||||
return stock;
|
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) {
|
_onMerchantClick(merchant) {
|
||||||
|
|
@ -402,6 +444,11 @@ export class MarketplaceScreen extends LitElement {
|
||||||
async _confirmBuy() {
|
async _confirmBuy() {
|
||||||
if (!this.selectedItem || !this.marketManager) return;
|
if (!this.selectedItem || !this.marketManager) return;
|
||||||
|
|
||||||
|
if (this.activeMerchant === "SELL") {
|
||||||
|
await this._confirmSell();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const success = await this.marketManager.buyItem(this.selectedItem.id);
|
const success = await this.marketManager.buyItem(this.selectedItem.id);
|
||||||
if (success) {
|
if (success) {
|
||||||
this._updateWallet();
|
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) {
|
_canAfford(item) {
|
||||||
return this.wallet.aetherShards >= item.price;
|
return this.wallet.aetherShards >= item.price;
|
||||||
}
|
}
|
||||||
|
|
@ -429,6 +489,30 @@ export class MarketplaceScreen extends LitElement {
|
||||||
return item.defId;
|
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() {
|
_dispatchClose() {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("market-closed", {
|
new CustomEvent("market-closed", {
|
||||||
|
|
@ -441,10 +525,12 @@ export class MarketplaceScreen extends LitElement {
|
||||||
render() {
|
render() {
|
||||||
const stock = this._getStock();
|
const stock = this._getStock();
|
||||||
const merchants = [
|
const merchants = [
|
||||||
{ id: "SMITH", icon: "⚔️", label: "Smith" },
|
{ id: "WEAPONS", icon: "⚔️", label: "Weapons" },
|
||||||
{ id: "TAILOR", icon: "🧥", label: "Tailor" },
|
{ id: "ARMOR", icon: "🛡️", label: "Armor" },
|
||||||
|
{ id: "ENGINEER", icon: "🔧", label: "Engineer" },
|
||||||
{ id: "ALCHEMIST", icon: "⚗️", label: "Alchemist" },
|
{ id: "ALCHEMIST", icon: "⚗️", label: "Alchemist" },
|
||||||
{ id: "BUYBACK", icon: "♻️", label: "Buyback" },
|
{ id: "BAZAAR", icon: "🏺", label: "Bazaar" },
|
||||||
|
{ id: "SELL", icon: "💰", label: "Sell Items" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const filters = [
|
const filters = [
|
||||||
|
|
@ -517,6 +603,13 @@ export class MarketplaceScreen extends LitElement {
|
||||||
${item.purchased
|
${item.purchased
|
||||||
? html`<div class="sold-overlay">SOLD</div>`
|
? html`<div class="sold-overlay">SOLD</div>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._getItemIcon(item)
|
||||||
|
? html`<img
|
||||||
|
src="${this._getItemIcon(item)}"
|
||||||
|
class="item-icon"
|
||||||
|
alt="${this._getItemName(item)}"
|
||||||
|
/>`
|
||||||
|
: html`<div class="item-icon">📦</div>`}
|
||||||
<div class="item-name">${this._getItemName(item)}</div>
|
<div class="item-name">${this._getItemName(item)}</div>
|
||||||
<div class="item-type">${item.type}</div>
|
<div class="item-type">${item.type}</div>
|
||||||
<div class="item-price">${item.price} 💎</div>
|
<div class="item-price">${item.price} 💎</div>
|
||||||
|
|
@ -535,7 +628,11 @@ export class MarketplaceScreen extends LitElement {
|
||||||
>
|
>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 class="modal-title">Confirm Purchase</h3>
|
<h3 class="modal-title">
|
||||||
|
${this.activeMerchant === "SELL"
|
||||||
|
? "Confirm Sell"
|
||||||
|
: "Confirm Purchase"}
|
||||||
|
</h3>
|
||||||
<button class="modal-close" @click=${this._closeModal}>
|
<button class="modal-close" @click=${this._closeModal}>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -547,10 +644,36 @@ export class MarketplaceScreen extends LitElement {
|
||||||
</div>
|
</div>
|
||||||
<div class="item-type">${this.selectedItem.type}</div>
|
<div class="item-type">${this.selectedItem.type}</div>
|
||||||
<div class="item-price">
|
<div class="item-price">
|
||||||
Price: ${this.selectedItem.price} 💎
|
${this.activeMerchant === "SELL"
|
||||||
|
? "Sell Value:"
|
||||||
|
: "Price:"}
|
||||||
|
${this.selectedItem.price} 💎
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item-description"
|
||||||
|
style="margin-top: 10px; font-style: italic; color: #aaa;"
|
||||||
|
>
|
||||||
|
${this._getItemDescription(this.selectedItem)}
|
||||||
|
</div>
|
||||||
|
<div class="item-stats" style="margin-top: 10px;">
|
||||||
|
${Object.entries(
|
||||||
|
this._getItemStats(this.selectedItem)
|
||||||
|
).map(
|
||||||
|
([stat, value]) => html`
|
||||||
|
<div
|
||||||
|
style="display: flex; justify-content: space-between; font-size: 12px; color: #ccc;"
|
||||||
|
>
|
||||||
|
<span style="text-transform: capitalize;"
|
||||||
|
>${stat}</span
|
||||||
|
>
|
||||||
|
<span style="color: #00ffff;">${value}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${!this._canAfford(this.selectedItem)
|
${this.activeMerchant !== "SELL" &&
|
||||||
|
!this._canAfford(this.selectedItem)
|
||||||
? html`<div style="color: var(--color-accent-red);">
|
? html`<div style="color: var(--color-accent-red);">
|
||||||
Insufficient funds!
|
Insufficient funds!
|
||||||
</div>`
|
</div>`
|
||||||
|
|
@ -559,10 +682,13 @@ export class MarketplaceScreen extends LitElement {
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
?disabled=${!this._canAfford(this.selectedItem)}
|
?disabled=${this.activeMerchant !== "SELL" &&
|
||||||
|
!this._canAfford(this.selectedItem)}
|
||||||
@click=${this._confirmBuy}
|
@click=${this._confirmBuy}
|
||||||
>
|
>
|
||||||
Buy for ${this.selectedItem.price} 💎
|
${this.activeMerchant === "SELL"
|
||||||
|
? `Sell for ${this.selectedItem.price} 💎`
|
||||||
|
: `Buy for ${this.selectedItem.price} 💎`}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" @click=${this._closeModal}>Cancel</button>
|
<button class="btn" @click=${this._closeModal}>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||