import { LitElement, html, css } from 'lit'; // Import Tier 1 Class Definitions import vanguardDef from '../assets/data/classes/vanguard.json' with { type: 'json' }; import weaverDef from '../assets/data/classes/aether_weaver.json' with { type: 'json' }; import scavengerDef from '../assets/data/classes/scavenger.json' with { type: 'json' }; import tinkerDef from '../assets/data/classes/tinker.json' with { type: 'json' }; import custodianDef from '../assets/data/classes/custodian.json' with { type: 'json' }; // UI Metadata Mapping const CLASS_METADATA = { 'CLASS_VANGUARD': { icon: '🛡️', image: 'assets/images/portraits/vanguard.png', role: 'Tank', description: 'A heavy frontline tank specialized in absorbing damage.' }, 'CLASS_WEAVER': { icon: '✨', image: 'assets/images/portraits/weaver.png', role: 'Magic DPS', description: 'A master of elemental magic capable of creating synergy chains.' }, 'CLASS_SCAVENGER': { icon: '🎒', image: 'assets/images/portraits/scavenger.png', role: 'Utility', description: 'Highly mobile utility expert who excels at finding loot.' }, 'CLASS_TINKER': { icon: '🔧', image: 'assets/images/portraits/tinker.png', role: 'Tech', description: 'Uses ancient technology to deploy turrets.' }, 'CLASS_CUSTODIAN': { icon: '🌿', image: 'assets/images/portraits/custodian.png', role: 'Healer', description: 'A spiritual healer focused on removing corruption.' } }; const RAW_TIER_1_CLASSES = [vanguardDef, weaverDef, scavengerDef, tinkerDef, custodianDef]; export class TeamBuilder extends LitElement { static get styles() { return css` :host { display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; font-family: 'Courier New', monospace; color: white; pointer-events: none; z-index: 10; box-sizing: border-box; } .container { display: grid; grid-template-columns: 280px 1fr 300px; grid-template-rows: 1fr 100px; grid-template-areas: "roster squad details" "footer footer footer"; height: 100%; width: 100%; pointer-events: auto; background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(4px); } @media (max-width: 1024px) { .container { grid-template-columns: 1fr; grid-template-rows: 200px 1fr 200px 80px; grid-template-areas: "roster" "squad" "details" "footer"; } } /* --- LEFT PANEL: ROSTER --- */ .roster-panel { grid-area: roster; background: rgba(20, 20, 30, 0.9); border-right: 2px solid #555; padding: 1rem; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; } h3 { margin-top: 0; color: #00ffff; border-bottom: 1px solid #555; padding-bottom: 10px; } .card { background: #333; border: 2px solid #555; padding: 15px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 15px; width: 100%; text-align: left; font-family: inherit; color: inherit; appearance: none; } .card:hover:not(:disabled) { border-color: #00ffff; background: #444; transform: translateX(5px); } .card.selected { border-color: #00ff00; background: #224422; } .card:disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(1); } /* --- CENTER PANEL: SQUAD SLOTS --- */ .squad-panel { grid-area: squad; display: flex; justify-content: center; align-items: center; padding: 2rem; gap: 30px; flex-wrap: wrap; } .slot-wrapper { position: relative; width: 180px; /* Wider for portraits */ height: 240px; /* Taller for portraits */ transition: transform 0.2s; } .slot-wrapper:hover { transform: scale(1.05); } .squad-slot { width: 100%; height: 100%; background: rgba(10, 10, 10, 0.8); border: 3px dashed #666; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; font-family: inherit; color: inherit; padding: 0; appearance: none; overflow: hidden; } /* Image placeholder style */ .unit-image { width: 100%; height: 75%; object-fit: cover; background-color: #222; border-bottom: 2px solid #555; } .unit-info { height: 25%; display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%; background: rgba(30,30,40,0.95); padding: 5px; box-sizing: border-box; } .squad-slot.filled { border: 3px solid #00ff00; background: rgba(0, 20, 0, 0.8); } .squad-slot.selected { border-color: #00ffff; box-shadow: 0 0 15px rgba(0,255,255,0.3); } .remove-btn { position: absolute; top: -12px; right: -12px; background: #cc0000; color: white; width: 28px; height: 28px; border: 2px solid white; border-radius: 50%; cursor: pointer; font-weight: bold; z-index: 2; } .placeholder-img { display: flex; align-items: center; justify-content: center; background: transparent; color: #555; font-size: 3rem; height: 100%; } /* --- RIGHT PANEL: DETAILS --- */ .details-panel { grid-area: details; background: rgba(20, 20, 30, 0.9); border-left: 2px solid #555; padding: 1.5rem; overflow-y: auto; } .footer { grid-area: footer; display: flex; justify-content: center; align-items: center; background: rgba(10, 10, 20, 0.95); border-top: 2px solid #555; } .embark-btn { padding: 15px 60px; font-size: 1.8rem; background: #008800; color: white; border: 3px solid #00ff00; cursor: pointer; text-transform: uppercase; font-weight: bold; font-family: inherit; letter-spacing: 2px; } .embark-btn:disabled { background: #333; border-color: #555; color: #777; cursor: not-allowed; } `; } static get properties() { return { mode: { type: String }, // 'DRAFT' (Classes) or 'ROSTER' (Existing Units) availablePool: { type: Array }, // List of Classes OR Units squad: { type: Array }, // The 4 slots selectedSlotIndex: { type: Number }, hoveredItem: { type: Object } }; } constructor() { super(); this.squad = [null, null, null, null]; this.selectedSlotIndex = 0; this.hoveredItem = null; this.mode = 'DRAFT'; // Default this.availablePool = []; } connectedCallback() { super.connectedCallback(); this._initializeData(); } /** * Configures the component based on provided data. */ _initializeData() { // 1. If we were passed an existing roster (e.g. from RosterManager), use it. if (this.availablePool && this.availablePool.length > 0) { this.mode = 'ROSTER'; console.log("TeamBuilder: Using Provided Roster", this.availablePool); return; } // 2. Default: Draft Mode (New Game) // Populate with Tier 1 classes this.mode = 'DRAFT'; this.availablePool = RAW_TIER_1_CLASSES.map(cls => { const meta = CLASS_METADATA[cls.id] || {}; return { ...cls, ...meta, unlocked: true }; }); console.log("TeamBuilder: Initializing Draft Mode"); } render() { const isSquadValid = this.squad.some(u => u !== null); return html`

${this.mode === 'DRAFT' ? 'Recruit Explorers' : 'Barracks Roster'}

${this.availablePool.map(item => { const isSelected = this.squad.some(s => s && (this.mode === 'ROSTER' ? s.id === item.id : false)); return html` `; })}
${this.squad.map((unit, index) => html`
${unit ? html`` : ''}
`)}
${this._renderDetails()}
`; } _renderDetails() { if (!this.hoveredItem) return html`

Hover over a unit to see details.

`; // Handle data structure diffs between ClassDef and UnitInstance const name = this.hoveredItem.name; const role = this.hoveredItem.role || this.hoveredItem.classId; const stats = this.hoveredItem.base_stats || this.hoveredItem.stats || {}; return html`

${name}

${role}


${this.hoveredItem.description || 'Ready for deployment.'}

Stats

`; } _selectSlot(index) { this.selectedSlotIndex = index; } _assignItem(item) { if (this.mode === 'DRAFT' && !item.unlocked) return; let unitManifest; if (this.mode === 'DRAFT') { // Create new unit definition unitManifest = { classId: item.id, name: item.name, icon: item.icon, image: item.image, // Pass image path role: item.role, isNew: true // Flag for GameLoop/Manager to generate ID }; } else { // Select existing unit // Try to recover image from CLASS_METADATA if not stored on unit instance const meta = CLASS_METADATA[item.classId] || {}; unitManifest = { id: item.id, classId: item.classId, name: item.name, icon: meta.icon, image: meta.image, role: meta.role, ...item }; } const newSquad = [...this.squad]; newSquad[this.selectedSlotIndex] = unitManifest; this.squad = newSquad; if (this.selectedSlotIndex < 3) this.selectedSlotIndex++; } _removeUnit(index) { const newSquad = [...this.squad]; newSquad[index] = null; this.squad = newSquad; this.selectedSlotIndex = index; } _handleEmbark() { const manifest = this.squad.filter(u => u !== null); this.dispatchEvent(new CustomEvent('embark', { detail: { squad: manifest, mode: this.mode }, bubbles: true, composed: true })); } // Helpers to make IDs readable (e.g. "ITEM_RUSTY_BLADE" -> "Rusty Blade") _formatItemName(id) { return id.replace('ITEM_', '').replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase()); } _formatSkillName(id) { return id.replace('SKILL_', '').replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase()); } } customElements.define('team-builder', TeamBuilder);