156 lines
4.9 KiB
Markdown
156 lines
4.9 KiB
Markdown
|
|
# **Marketplace Specification: The Gilded Bazaar**
|
||
|
|
|
||
|
|
This document defines the architecture, logic, and UI for the Hub Marketplace. It acts as the primary gold sink and progression accelerator.
|
||
|
|
|
||
|
|
## **1. System Architecture**
|
||
|
|
|
||
|
|
The Marketplace is managed by a new singleton logic controller: **MarketManager**.
|
||
|
|
|
||
|
|
- **Location:** src/managers/MarketManager.js
|
||
|
|
- **Owner:** GameStateManager (instantiated alongside RosterManager).
|
||
|
|
- **Persistence:** The current stock and buyback queue are saved to IndexedDB (market_state) to prevent players from reloading the game to re-roll shop inventory.
|
||
|
|
|
||
|
|
### **Integration Flow**
|
||
|
|
|
||
|
|
1. **Mission Complete:** MissionManager triggers an event. MarketManager listens and sets a needsRefresh flag.
|
||
|
|
2. **Hub Entry:** Player enters Hub. MarketManager checks flag. If true, generates new stock and saves immediately.
|
||
|
|
3. **UI Render:** HubScreen passes the MarketManager instance to the <marketplace-screen> component.
|
||
|
|
|
||
|
|
## **2. TypeScript Interfaces (Data Model)**
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// src/types/Marketplace.ts
|
||
|
|
|
||
|
|
import { ItemType, Rarity, ItemInstance } from "./Inventory";
|
||
|
|
|
||
|
|
export type MerchantType = "SMITH" | "TAILOR" | "ALCHEMIST" | "SCAVENGER";
|
||
|
|
|
||
|
|
export interface MarketState {
|
||
|
|
/** Timestamp or Mission Count when this stock was generated */
|
||
|
|
generationId: string;
|
||
|
|
/** The active inventory for sale */
|
||
|
|
stock: MarketItem[];
|
||
|
|
/** Items sold by the player this session (can be bought back) */
|
||
|
|
buyback: MarketItem[]; // Price is usually equal to sell price
|
||
|
|
/*_ Daily Deal or Special logic */
|
||
|
|
specialOffer?: string; // ID of a specific item
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface MarketItem {
|
||
|
|
id: string; // Unique Stock ID (e.g. "STOCK_001")
|
||
|
|
defId: string; // Reference to ItemRegistry (e.g. "ITEM_RUSTY_BLADE")
|
||
|
|
type: ItemType; // Cached for filtering
|
||
|
|
rarity: Rarity; // Cached for sorting/styling
|
||
|
|
price: number;
|
||
|
|
discount: number; // 0.0 to 1.0 (percent off)
|
||
|
|
purchased: boolean; // If true, show as "Sold Out"
|
||
|
|
|
||
|
|
/** If this is a specific instance (e.g. Buyback), store the data here */
|
||
|
|
instanceData?: ItemInstance;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** _ Configuration for what a merchant sells at a specific Game Stage.
|
||
|
|
*/
|
||
|
|
export interface StockTable {
|
||
|
|
minItems: number;
|
||
|
|
maxItems: number;
|
||
|
|
rarityWeights: {
|
||
|
|
COMMON: number;
|
||
|
|
UNCOMMON: number;
|
||
|
|
RARE: number;
|
||
|
|
ANCIENT: number;
|
||
|
|
};
|
||
|
|
allowedTypes: ItemType[];
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## **3. Logic & Algorithms**
|
||
|
|
|
||
|
|
### **A. Stock Generation (`generateStock(tier)`)**
|
||
|
|
|
||
|
|
Triggered only when a run is completed.
|
||
|
|
|
||
|
|
**Tier 1 (Early Game):**
|
||
|
|
|
||
|
|
- **Smith:** 5 Common Weapons, 3 Common Armor.
|
||
|
|
- **Alchemist:** 5 Potions (Stacked), 2 Grenades.
|
||
|
|
|
||
|
|
**Tier 2 (Mid Game - After Mission 3):**
|
||
|
|
|
||
|
|
- **Weights:** Common (60%), Uncommon (30%), Rare (10%).
|
||
|
|
- **Scavenger:** Unlocks. Sells 3 "Mystery Box" items (Unidentified Relics).
|
||
|
|
|
||
|
|
**Algorithm:**
|
||
|
|
|
||
|
|
1. Filter `ItemRegistry` by `allowedTypes` and `Tier`.
|
||
|
|
2. Roll `RNG` against `rarityWeights`.
|
||
|
|
3. Select random Item ID.
|
||
|
|
4. Calculate Price: `BaseValue * (1 + RandomVariance(0.1))`.
|
||
|
|
5. Create `MarketItem`.
|
||
|
|
|
||
|
|
### **B. Transaction Processing (`buyItem`, `sellItem`)**
|
||
|
|
|
||
|
|
Transactions must be atomic to prevent item duplication or currency desync.
|
||
|
|
|
||
|
|
**Buy Logic:**
|
||
|
|
|
||
|
|
1. Check `Wallet >= Price`.
|
||
|
|
2. Deduct Currency.
|
||
|
|
3. **Generate Instance:** Create a new `ItemInstance` with a fresh UID (`ITEM_{TIMESTAMP}`).
|
||
|
|
4. Add to `InventoryManager.hubStash`.
|
||
|
|
5. Mark `MarketItem` as `purchased: true`.
|
||
|
|
6. Save State.
|
||
|
|
|
||
|
|
**Sell Logic:**
|
||
|
|
|
||
|
|
1. Remove `ItemInstance` from `hubStash`.
|
||
|
|
2. Calculate Value (`BasePrice * 0.25`).
|
||
|
|
3. Add Currency.
|
||
|
|
4. **Create Buyback:** Convert instance to `MarketItem` and add to `buyback` array (Limit 10).
|
||
|
|
5. Save State.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## **4. UI Implementation (LitElement)**
|
||
|
|
|
||
|
|
**Component:** `src/ui/screens/MarketplaceScreen.js`
|
||
|
|
|
||
|
|
### **Visual Layout**
|
||
|
|
|
||
|
|
- **Grid Container:** CSS Grid `250px 1fr`.
|
||
|
|
- **Sidebar (Merchants):** Vertical tabs.
|
||
|
|
- [⚔️ Smith]
|
||
|
|
- [🧥 Tailor]
|
||
|
|
- [⚗️ Alchemist]
|
||
|
|
- [♻️ Buyback]
|
||
|
|
- **Main Content:**
|
||
|
|
- **Filter Bar:** "Show All", "Weapons Only", etc.
|
||
|
|
- **Item Grid:** Flex-wrap container of Item Cards.
|
||
|
|
- **Player Panel (Right overlay or bottom slide-up):**
|
||
|
|
- Shows current Inventory. Drag-and-drop to "Sell Zone" or Right-Click to sell.
|
||
|
|
|
||
|
|
### **Interactive States**
|
||
|
|
|
||
|
|
- **Affordable:** Price Text is White/Green.
|
||
|
|
- **Unaffordable:** Price Text is Red. Button Disabled.
|
||
|
|
- **Sold Out:** Card is dimmed, overlay text "SOLD".
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## **5. Conditions of Acceptance (CoA)**
|
||
|
|
|
||
|
|
**CoA 1: Persistence Integrity**
|
||
|
|
|
||
|
|
- Buying an item, saving, and reloading the page must result in the item being in the Inventory and the Shop still showing "Sold Out".
|
||
|
|
- The shop stock **must not change** upon reload.
|
||
|
|
|
||
|
|
**CoA 2: Currency Math**
|
||
|
|
|
||
|
|
- Buying an item costs exactly the listed price.
|
||
|
|
- Selling an item refunds exactly the calculated sell price.
|
||
|
|
- Buyback allows repurchasing a sold item for the _exact amount it was sold for_ (Undo button logic).
|
||
|
|
|
||
|
|
**CoA 3: Class Filtering**
|
||
|
|
|
||
|
|
- (Optional Polish) The shop should visually flag items that cannot be equipped by _anyone_ in the current Roster (e.g. "No Sapper recruited").
|