feat(dashboard): replace quick actions with team synergy analysis and interactive gm console dispatcher
This commit is contained in:
1 parent
039609980e
commit
bd367a11cd
3 files changed
+765
-94
No files matched your search
+188
@@ -2502,5 +2502,193 @@ input:checked + .slider:before {
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
Team Combat Synergy & Role Balance
|
||||
========================================== */
|
||||
.synergy-elements-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(85px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.synergy-element-chip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.35rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.synergy-element-chip.active {
|
||||
background: rgba(56, 189, 248, 0.08);
|
||||
border-color: rgba(56, 189, 248, 0.4);
|
||||
box-shadow: 0 0 10px rgba(56, 189, 248, 0.15);
|
||||
}
|
||||
|
||||
.synergy-element-chip.inactive {
|
||||
opacity: 0.35;
|
||||
filter: grayscale(80%);
|
||||
}
|
||||
|
||||
.synergy-element-name {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.synergy-element-chars {
|
||||
font-size: 0.675rem;
|
||||
color: var(--accent-cyan);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.synergy-metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.synergy-metric-box {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.5rem 0.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.synergy-metric-val {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 800;
|
||||
color: var(--accent-gold);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.synergy-metric-lbl {
|
||||
font-size: 0.675rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
GM Command Dispatcher & Live Log Console
|
||||
========================================== */
|
||||
.console-quick-pills {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.console-pill-btn {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #e2e8f0;
|
||||
padding: 0.3rem 0.65rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.775rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.console-pill-btn:hover {
|
||||
background: rgba(56, 189, 248, 0.15);
|
||||
border-color: rgba(56, 189, 248, 0.4);
|
||||
color: var(--accent-cyan);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.console-terminal-wrapper {
|
||||
background: #070b12;
|
||||
border: 1px solid rgba(56, 189, 248, 0.25);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.console-terminal-header {
|
||||
background: #0f172a;
|
||||
padding: 0.4rem 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.terminal-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dot-red { background: #ef4444; }
|
||||
.dot-yellow { background: #f59e0b; }
|
||||
.dot-green { background: #10b981; }
|
||||
|
||||
.terminal-title {
|
||||
font-size: 0.725rem;
|
||||
color: var(--text-muted);
|
||||
font-family: monospace;
|
||||
margin-left: 0.35rem;
|
||||
}
|
||||
|
||||
.console-terminal-log {
|
||||
height: 200px;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
padding: 0.75rem;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.775rem;
|
||||
line-height: 1.55;
|
||||
color: #cbd5e1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #64748b;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.log-tag {
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
padding: 0 0.3rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.log-tag.tag-sys { background: rgba(56, 189, 248, 0.15); color: #38bdf8; }
|
||||
.log-tag.tag-cmd { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }
|
||||
.log-tag.tag-ok { background: rgba(52, 211, 153, 0.15); color: #34d399; }
|
||||
.log-tag.tag-err { background: rgba(248, 113, 113, 0.15); color: #f87171; }
|
||||
.log-tag.tag-net { background: rgba(168, 85, 247, 0.15); color: #c084fc; }
|
||||
|
||||
.log-msg {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+141
-88
@@ -104,109 +104,162 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<!-- Server & Map Card -->
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span data-i18n="serverStatusTitle">📍 สถานะเซิร์ฟเวอร์ & พิกัดปัจจุบัน</span>
|
||||
<span class="btn-sm btn-secondary" id="badge-version">API Live</span>
|
||||
<div class="grid-2" style="align-items: start;">
|
||||
<!-- Column 1: Server Status & Team Combat Synergy -->
|
||||
<div style="display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
<!-- 1. Server & Map Card -->
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span data-i18n="serverStatusTitle">📍 สถานะเซิร์ฟเวอร์ & พิกัดปัจจุบัน</span>
|
||||
<span class="btn-sm btn-secondary" id="badge-version">API Live</span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblServerModeActive">โหมดเซิร์ฟเวอร์ที่ใช้งาน</span>
|
||||
<strong id="lbl-active-mode" style="color: var(--accent-cyan);">GaC-hkrpg-go</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblApiEndpoint">API Gateway Endpoint</span>
|
||||
<code id="lbl-api-url" style="color: var(--accent-cyan); font-size: 0.9rem;">-</code>
|
||||
</div>
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblPlaneId">Plane ID</span>
|
||||
<span id="lbl-plane-id" style="font-weight: bold;">-</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblFloorId">Floor ID</span>
|
||||
<span id="lbl-floor-id" style="font-weight: bold;">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="form-label" data-i18n="coords">พิกัดตัวละคร XYZ</span>
|
||||
<span id="lbl-coordinates" style="color: var(--text-muted); font-size: 0.85rem;">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblServerModeActive">โหมดเซิร์ฟเวอร์ที่ใช้งาน</span>
|
||||
<strong id="lbl-active-mode" style="color: var(--accent-cyan);">GaC-hkrpg-go</strong>
|
||||
|
||||
<!-- 2. Team Combat Synergy & Weakness Break Analysis -->
|
||||
<div class="card" id="card-team-synergy">
|
||||
<div class="card-title" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<span style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span>⚔️</span>
|
||||
<span>วิเคราะห์ Synergy ทีม & จุดอ่อน</span>
|
||||
</span>
|
||||
<span class="badge-super-tag" id="badge-synergy-coverage" style="background: rgba(56, 189, 248, 0.15); color: var(--accent-cyan); font-weight: 800;">ครอบคลุม 0/7 ธาตุ</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblApiEndpoint">API Gateway Endpoint</span>
|
||||
<code id="lbl-api-url" style="color: var(--accent-cyan); font-size: 0.9rem;">-</code>
|
||||
</div>
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblPlaneId">Plane ID</span>
|
||||
<span id="lbl-plane-id" style="font-weight: bold;">-</span>
|
||||
<p class="card-subtitle" style="margin-bottom: 0.85rem;">วิเคราะห์การครอบคลุม Weakness Break, ความสมดุลของบทบาท และความพร้อมของทีม</p>
|
||||
|
||||
<!-- A. Weakness Break Coverage 7 Elements -->
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem;">
|
||||
🎯 การทำลายจุดอ่อน (Weakness Break Coverage)
|
||||
</div>
|
||||
<div>
|
||||
<span class="form-label" data-i18n="lblFloorId">Floor ID</span>
|
||||
<span id="lbl-floor-id" style="font-weight: bold;">-</span>
|
||||
<div class="synergy-elements-grid" id="synergy-elements-container">
|
||||
<!-- Rendered dynamically by JS: Physical, Fire, Ice, Lightning, Wind, Quantum, Imaginary -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- B. Team Role Balance -->
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem;">
|
||||
🛡️ การกระจายบทบาทในทีม (Team Composition & Roles)
|
||||
</div>
|
||||
<div id="synergy-roles-container" style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem;">
|
||||
<!-- Role tags: DPS, Sub-DPS, Buffer/Debuffer, Sustain -->
|
||||
</div>
|
||||
<div id="synergy-advice-box" style="padding: 0.6rem 0.8rem; border-radius: 6px; font-size: 0.8rem; line-height: 1.4; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08);">
|
||||
<!-- Dynamic Advice -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- C. Equipment Readiness & Stats Overview -->
|
||||
<div>
|
||||
<span class="form-label" data-i18n="coords">พิกัดตัวละคร XYZ</span>
|
||||
<span id="lbl-coordinates" style="color: var(--text-muted); font-size: 0.85rem;">-</span>
|
||||
<div style="font-size: 0.775rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem;">
|
||||
📊 ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย
|
||||
</div>
|
||||
<div class="synergy-metrics-grid">
|
||||
<div class="synergy-metric-box">
|
||||
<div class="synergy-metric-val" id="synergy-avg-level">-</div>
|
||||
<div class="synergy-metric-lbl">เลเวลเฉลี่ย</div>
|
||||
</div>
|
||||
<div class="synergy-metric-box">
|
||||
<div class="synergy-metric-val" id="synergy-avg-eidolon">-</div>
|
||||
<div class="synergy-metric-lbl">Eidolon เฉลี่ย</div>
|
||||
</div>
|
||||
<div class="synergy-metric-box">
|
||||
<div class="synergy-metric-val" id="synergy-lc-ready">-</div>
|
||||
<div class="synergy-metric-lbl">สวมกรวยแสง</div>
|
||||
</div>
|
||||
<div class="synergy-metric-box">
|
||||
<div class="synergy-metric-val" id="synergy-relic-ready">-</div>
|
||||
<div class="synergy-metric-lbl">รีลิกส์ครบชุด</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions Card -->
|
||||
<div class="card">
|
||||
<div class="card-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span data-i18n="quickActionsTitle">⚡ ดำเนินการด่วน</span>
|
||||
<span class="btn-sm btn-secondary" style="font-size: 0.75rem;" data-i18n="quickActionsBadge">คำสั่งด่วน</span>
|
||||
<!-- Column 2: GM Command Dispatcher & Live Log Console -->
|
||||
<div class="card" id="card-gm-console" style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
<div class="card-title" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<span style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span>💻</span>
|
||||
<span>คอนโซลคำสั่ง GM & Log สด</span>
|
||||
</span>
|
||||
<div style="display: flex; gap: 0.35rem;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" id="btn-console-copy-log" title="คัดลอก Log ทั้งหมด" style="padding: 0.2rem 0.6rem; font-size: 0.75rem;">
|
||||
📋 คัดลอก
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" id="btn-console-clear-log" title="ล้างหน้าจอ Terminal" style="padding: 0.2rem 0.6rem; font-size: 0.75rem;">
|
||||
🗑️ ล้าง Log
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-subtitle" data-i18n="quickActionsSubtitle">เครื่องมือจัดการทีมและเซิร์ฟเวอร์แบบทันที</p>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 0.75rem;">
|
||||
<!-- 1. Set Team Lineup to E0S1 (Standard) -->
|
||||
<button type="button" class="btn btn-primary" id="btn-quick-lineup-e0s1" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem;">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">⚡</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem;" data-i18n="qaLineupE0S1Title">ตั้งค่าทีมเป็น E0S1</div>
|
||||
<div style="font-size: 0.725rem; opacity: 0.9;" data-i18n="qaLineupE0S1Desc">ปรับตัวละครในทีมเป็น Lv.80, E0, S1 สกิลเต็ม</div>
|
||||
</div>
|
||||
</button>
|
||||
<p class="card-subtitle" style="margin-bottom: 0.25rem;">ส่งคำสั่ง GM / Server commands แบบเรียลไทม์ พร้อมติดตามสถานะและ Packet Log</p>
|
||||
|
||||
<!-- 2. Equip Recommended Relics for Team -->
|
||||
<button type="button" class="btn btn-secondary" id="btn-quick-lineup-relics" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem;">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">🛡️</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem;" data-i18n="qaLineupRelicsTitle">ติดตั้งรีลิกส์แนะนำทั้งทีม</div>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted);" data-i18n="qaLineupRelicsDesc">สวมใส่รีลิกส์และเครื่องประดับให้ทุกคนในทีม</div>
|
||||
</div>
|
||||
</button>
|
||||
<!-- Quick GM Command Pills -->
|
||||
<div>
|
||||
<div style="font-size: 0.75rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.35rem;">
|
||||
⚡ คำสั่งด่วนที่ใช้บ่อย (Quick Commands)
|
||||
</div>
|
||||
<div class="console-quick-pills">
|
||||
<button type="button" class="console-pill-btn" data-cmd="/heal" title="ฟื้นฟู HP/Energy ทั้งทีม">💖 /heal</button>
|
||||
<button type="button" class="console-pill-btn" data-cmd="/give 102 100000" title="รับ 100,000 Stellar Jades">💎 +100k Jades</button>
|
||||
<button type="button" class="console-pill-btn" data-cmd="/give 2 10000000" title="รับ 10,000,000 Credits">💰 +10M Credits</button>
|
||||
<button type="button" class="console-pill-btn" data-cmd="/max" title="อัปเต็มเลเวล 80 E6 ทุกตัว">⚡ /max</button>
|
||||
<button type="button" class="console-pill-btn" data-cmd="/teleport 20001 1" title="วาร์ปไปรถไฟ Astral Express">🚂 รถไฟ Express</button>
|
||||
<button type="button" class="console-pill-btn" data-cmd="/clear lineup" title="ล้างตัวละครในทีม">🧹 เคลียร์ทีม</button>
|
||||
<button type="button" class="console-pill-btn" data-cmd="/help" title="ดูรายการคำสั่งทั้งหมด">❓ /help</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Unlock All Avatars -->
|
||||
<button type="button" class="btn btn-gold" id="btn-quick-unlock-all-avatars" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem;">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">👥</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem;" data-i18n="qaUnlockAllTitle">ปลดล็อกตัวละครครบทุกตัว</div>
|
||||
<div style="font-size: 0.725rem; opacity: 0.85;" data-i18n="qaUnlockAllDesc">เพิ่มครบ 70+ ตัว E6 เต็มคลัง</div>
|
||||
</div>
|
||||
</button>
|
||||
<!-- Interactive Terminal Screen -->
|
||||
<div class="console-terminal-wrapper">
|
||||
<div class="console-terminal-header">
|
||||
<span class="terminal-dot dot-red"></span>
|
||||
<span class="terminal-dot dot-yellow"></span>
|
||||
<span class="terminal-dot dot-green"></span>
|
||||
<span class="terminal-title">GaC-hkrpg-go Console Session (Interactive GM Dispatcher)</span>
|
||||
</div>
|
||||
<div class="console-terminal-log" id="console-terminal-log">
|
||||
<!-- Logs rendered here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. Add All 5-Star Light Cones -->
|
||||
<button type="button" class="btn btn-secondary" id="btn-quick-add-all-lcs" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem;">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">🗡️</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem;" data-i18n="qaAddLcsTitle">เพิ่มกรวยแสง 5★ ทั้งหมด</div>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted);" data-i18n="qaAddLcsDesc">กรวยแสงประจำตัว S5 เข้าคลัง</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- 5. Teleport to Astral Express -->
|
||||
<button type="button" class="btn btn-secondary" id="btn-quick-teleport-train" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem;">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">🚂</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem;" data-i18n="qaTeleportTitle">วาร์ปกลับรถไฟ Astral Express</div>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted);" data-i18n="qaTeleportDesc">กลับสู่ Parlor Car จุดปลอดภัย</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- 6. Clear Lineup Slots -->
|
||||
<button type="button" class="btn btn-danger" id="btn-quick-clear-lineup" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem;">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">🧹</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem;" data-i18n="qaClearLineupTitle">ล้างตัวละครในทีม</div>
|
||||
<div style="font-size: 0.725rem; opacity: 0.85;" data-i18n="qaClearLineupDesc">เคลียร์ทุกช่องเพื่อจัดทีมใหม่</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- 7. Gachabase Game Data & Asset Sync -->
|
||||
<button type="button" class="btn btn-secondary" id="btn-quick-gachabase-sync" style="display: flex; align-items: center; justify-content: flex-start; gap: 0.6rem; text-align: left; padding: 0.75rem 0.9rem; border-color: rgba(245, 158, 11, 0.4);">
|
||||
<span style="font-size: 1.35rem; line-height: 1;">🌐</span>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.875rem; color: #fbbf24;" data-i18n="qaGachabaseTitle">ซิงค์ข้อมูลเกม Gachabase</div>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted);" data-i18n="qaGachabaseDesc">อัปเดตข้อมูล & รูปภาพเมื่อเกมมีแพตช์ใหม่</div>
|
||||
</div>
|
||||
<!-- Command Input Bar -->
|
||||
<form id="form-console-cmd" style="display: flex; gap: 0.5rem; align-items: center; margin-top: 0.25rem;">
|
||||
<div class="console-input-wrap" style="flex: 1; display: flex; align-items: center; background: rgba(0,0,0,0.4); border: 1px solid var(--border-color); border-radius: 6px; padding: 0 0.75rem;">
|
||||
<span style="color: var(--accent-cyan); font-family: monospace; font-weight: 800; font-size: 1.1rem; margin-right: 0.5rem;">></span>
|
||||
<input type="text" id="console-cmd-input" class="form-input" placeholder="พิมพ์คำสั่ง GM เช่น /heal, /max, /teleport 20411, /give 102 1000..." style="background: transparent; border: none; padding: 0.6rem 0; font-family: monospace; font-size: 0.85rem; box-shadow: none;" autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="btn-console-cmd-send" style="padding: 0.6rem 1.25rem; font-weight: 700; white-space: nowrap;">
|
||||
ส่งคำสั่ง
|
||||
</button>
|
||||
</form>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted); display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>💡 รองรับกด <kbd style="background: rgba(255,255,255,0.1); padding: 0.1rem 0.35rem; border-radius: 3px; font-family: monospace;">↑</kbd> <kbd style="background: rgba(255,255,255,0.1); padding: 0.1rem 0.35rem; border-radius: 3px; font-family: monospace;">↓</kbd> เพื่อเลือกประวัติคำสั่งก่อนหน้า</span>
|
||||
<span id="lbl-console-conn-status" style="color: var(--accent-green);">● เซิร์ฟเวอร์ออนไลน์</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,12 +72,26 @@ const el = {
|
||||
lineupContainer: document.getElementById('lineup-container'),
|
||||
toggleCreativeMode: document.getElementById('toggle-creative-mode'),
|
||||
badgeCreativeMode: document.getElementById('badge-creative-mode'),
|
||||
btnQuickLineupE0S1: document.getElementById('btn-quick-lineup-e0s1'),
|
||||
btnQuickLineupRelics: document.getElementById('btn-quick-lineup-relics'),
|
||||
btnQuickUnlockAllAvatars: document.getElementById('btn-quick-unlock-all-avatars'),
|
||||
btnQuickAddAllLcs: document.getElementById('btn-quick-add-all-lcs'),
|
||||
btnQuickTeleportTrain: document.getElementById('btn-quick-teleport-train'),
|
||||
btnQuickClearLineup: document.getElementById('btn-quick-clear-lineup'),
|
||||
// Team Combat Synergy
|
||||
cardTeamSynergy: document.getElementById('card-team-synergy'),
|
||||
badgeSynergyCoverage: document.getElementById('badge-synergy-coverage'),
|
||||
synergyElementsContainer: document.getElementById('synergy-elements-container'),
|
||||
synergyRolesContainer: document.getElementById('synergy-roles-container'),
|
||||
synergyAdviceBox: document.getElementById('synergy-advice-box'),
|
||||
synergyAvgLevel: document.getElementById('synergy-avg-level'),
|
||||
synergyAvgEidolon: document.getElementById('synergy-avg-eidolon'),
|
||||
synergyLcReady: document.getElementById('synergy-lc-ready'),
|
||||
synergyRelicReady: document.getElementById('synergy-relic-ready'),
|
||||
|
||||
// GM Command Dispatcher & Live Log Console
|
||||
cardGmConsole: document.getElementById('card-gm-console'),
|
||||
btnConsoleCopyLog: document.getElementById('btn-console-copy-log'),
|
||||
btnConsoleClearLog: document.getElementById('btn-console-clear-log'),
|
||||
consoleTerminalLog: document.getElementById('console-terminal-log'),
|
||||
formConsoleCmd: document.getElementById('form-console-cmd'),
|
||||
consoleCmdInput: document.getElementById('console-cmd-input'),
|
||||
btnConsoleCmdSend: document.getElementById('btn-console-cmd-send'),
|
||||
lblConsoleConnStatus: document.getElementById('lbl-console-conn-status'),
|
||||
// Gachabase Sync
|
||||
btnHeaderGachabaseSync: document.getElementById('btn-header-gachabase-sync'),
|
||||
btnQuickGachabaseSync: document.getElementById('btn-quick-gachabase-sync'),
|
||||
@@ -379,10 +393,18 @@ function setConnectionStatus(connected, extraText = '') {
|
||||
el.statusDot.classList.add('connected');
|
||||
el.connectionStatusText.textContent = extraText || t('online');
|
||||
el.connectionStatusText.style.color = 'var(--accent-green)';
|
||||
if (el.lblConsoleConnStatus) {
|
||||
el.lblConsoleConnStatus.textContent = '● เซิร์ฟเวอร์ออนไลน์';
|
||||
el.lblConsoleConnStatus.style.color = 'var(--accent-green)';
|
||||
}
|
||||
} else {
|
||||
el.statusDot.classList.remove('connected');
|
||||
el.connectionStatusText.textContent = extraText || t('offline');
|
||||
el.connectionStatusText.style.color = 'var(--accent-red)';
|
||||
if (el.lblConsoleConnStatus) {
|
||||
el.lblConsoleConnStatus.textContent = '○ ออฟไลน์ (คำสั่งทำงานผ่านแคชภายใน)';
|
||||
el.lblConsoleConnStatus.style.color = 'var(--text-muted)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,6 +1037,413 @@ function renderLineup(lineups) {
|
||||
});
|
||||
el.lineupContainer.appendChild(addSlotCard);
|
||||
}
|
||||
|
||||
// Update Team Combat Synergy Analysis
|
||||
renderTeamSynergy(currentLineup);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Team Combat Synergy & Weakness Break Analysis
|
||||
// ==========================================
|
||||
const HSR_ELEMENTS = [
|
||||
{ key: 'Physical', name: 'Physical', nameTh: 'กายภาพ', color: '#cbd5e1' },
|
||||
{ key: 'Fire', name: 'Fire', nameTh: 'ไฟ', color: '#ef4444' },
|
||||
{ key: 'Ice', name: 'Ice', nameTh: 'น้ำแข็ง', color: '#38bdf8' },
|
||||
{ key: 'Lightning', name: 'Lightning', nameTh: 'สายฟ้า', color: '#c084fc' },
|
||||
{ key: 'Wind', name: 'Wind', nameTh: 'ลม', color: '#34d399' },
|
||||
{ key: 'Quantum', name: 'Quantum', nameTh: 'ควอนตัม', color: '#818cf8' },
|
||||
{ key: 'Imaginary', name: 'Imaginary', nameTh: 'จินตภาพ', color: '#fbbf24' }
|
||||
];
|
||||
|
||||
function renderTeamSynergy(lineups) {
|
||||
if (!el.synergyElementsContainer) return;
|
||||
const currentLineup = lineups || state.statusData?.lineups || DEFAULT_LINEUP;
|
||||
|
||||
// Extract active character IDs
|
||||
const activeIds = [];
|
||||
for (const slot in currentLineup) {
|
||||
const aid = parseInt(currentLineup[slot], 10);
|
||||
if (aid && aid > 0 && !activeIds.includes(aid)) {
|
||||
activeIds.push(aid);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Weakness Break Coverage
|
||||
const coveredElements = {};
|
||||
const roles = {
|
||||
dps: [], // Destruction, Hunt, Erudition
|
||||
specialist: [], // Remembrance, Elation
|
||||
support: [], // Harmony, Nihility
|
||||
sustain: [] // Preservation, Abundance
|
||||
};
|
||||
|
||||
let totalLevel = 0;
|
||||
let totalEidolon = 0;
|
||||
let lcCount = 0;
|
||||
let fullRelicCount = 0;
|
||||
|
||||
activeIds.forEach(aid => {
|
||||
const meta = getAvatarInfo(aid);
|
||||
const av = state.fullFreesrData?.avatars?.[aid];
|
||||
const lvl = av?.level || 80;
|
||||
const rank = av?.data?.rank ?? (state.avatarsList.find(a => a.avatar_id === aid)?.rank ?? 0);
|
||||
|
||||
totalLevel += lvl;
|
||||
totalEidolon += rank;
|
||||
|
||||
// Element coverage
|
||||
const elem = meta.element || 'Unknown';
|
||||
if (!coveredElements[elem]) {
|
||||
coveredElements[elem] = [];
|
||||
}
|
||||
coveredElements[elem].push(meta.name);
|
||||
|
||||
// Path / Role
|
||||
const path = meta.path || '';
|
||||
if (['Destruction', 'Hunt', 'Erudition'].includes(path)) {
|
||||
roles.dps.push(meta.name);
|
||||
} else if (['Remembrance', 'Elation'].includes(path)) {
|
||||
roles.specialist.push(meta.name);
|
||||
} else if (['Harmony', 'Nihility'].includes(path)) {
|
||||
roles.support.push(meta.name);
|
||||
} else if (['Preservation', 'Abundance'].includes(path)) {
|
||||
roles.sustain.push(meta.name);
|
||||
}
|
||||
|
||||
// LC check
|
||||
const hasLc = (state.fullFreesrData?.lightcones || []).some(l => l.equip_avatar === aid);
|
||||
if (hasLc) lcCount++;
|
||||
|
||||
// Relics check (count how many relics equipped)
|
||||
const relCount = (state.fullFreesrData?.relics || []).filter(r => r.equip_avatar === aid).length;
|
||||
if (relCount >= 6) fullRelicCount++;
|
||||
});
|
||||
|
||||
const count = activeIds.length || 1;
|
||||
const avgLevel = activeIds.length ? (totalLevel / count).toFixed(1) : '-';
|
||||
const avgEidolon = activeIds.length ? `E${(totalEidolon / count).toFixed(1)}` : '-';
|
||||
|
||||
// Render Element Chips
|
||||
const coveredCount = Object.keys(coveredElements).length;
|
||||
if (el.badgeSynergyCoverage) {
|
||||
el.badgeSynergyCoverage.textContent = `ครอบคลุม ${coveredCount}/7 ธาตุ`;
|
||||
el.badgeSynergyCoverage.style.color = coveredCount >= 4 ? 'var(--accent-green)' : (coveredCount >= 2 ? 'var(--accent-cyan)' : 'var(--accent-gold)');
|
||||
}
|
||||
|
||||
el.synergyElementsContainer.innerHTML = HSR_ELEMENTS.map(elem => {
|
||||
const isCovered = !!coveredElements[elem.key];
|
||||
const providers = coveredElements[elem.key] || [];
|
||||
const providerText = isCovered ? providers.join(', ') : 'ไม่มีในทีม';
|
||||
return `
|
||||
<div class="synergy-element-chip ${isCovered ? 'active' : 'inactive'}" style="${isCovered ? `border-color: ${elem.color}66; box-shadow: 0 0 8px ${elem.color}22;` : ''}">
|
||||
${getElementIconHtml(elem.key, 22)}
|
||||
<span class="synergy-element-name" style="${isCovered ? `color: ${elem.color};` : ''}">${elem.nameTh}</span>
|
||||
<span class="synergy-element-chars" title="${providerText}">${providerText}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Render Role Tags
|
||||
if (el.synergyRolesContainer) {
|
||||
el.synergyRolesContainer.innerHTML = `
|
||||
<div class="badge-super-tag" style="background: rgba(239, 68, 68, 0.15); color: #f87171; font-weight: 700;">
|
||||
🗡️ DPS (${roles.dps.length}): ${roles.dps.join(', ') || '-'}
|
||||
</div>
|
||||
<div class="badge-super-tag" style="background: rgba(168, 85, 247, 0.15); color: #c084fc; font-weight: 700;">
|
||||
⚡ ซัพพอร์ต/ดีบัฟ (${roles.support.length}): ${roles.support.join(', ') || '-'}
|
||||
</div>
|
||||
<div class="badge-super-tag" style="background: rgba(52, 211, 153, 0.15); color: #34d399; font-weight: 700;">
|
||||
🛡️ Sustain ฮีล/ชิลด์ (${roles.sustain.length}): ${roles.sustain.join(', ') || '-'}
|
||||
</div>
|
||||
${roles.specialist.length ? `
|
||||
<div class="badge-super-tag" style="background: rgba(56, 189, 248, 0.15); color: #38bdf8; font-weight: 700;">
|
||||
🔮 พิเศษ/FUA (${roles.specialist.length}): ${roles.specialist.join(', ')}
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
// Dynamic Advice Text
|
||||
if (el.synergyAdviceBox) {
|
||||
if (activeIds.length === 0) {
|
||||
el.synergyAdviceBox.innerHTML = `<span style="color: var(--text-muted);">ยังไม่มีตัวละครในทีม กรุณาเลือกตัวละครในทีมด้านบน</span>`;
|
||||
} else if (roles.sustain.length === 0) {
|
||||
el.synergyAdviceBox.innerHTML = `<span style="color: #fbbf24;">⚠️ <strong>ข้อแนะนำ:</strong> ทีมยังขาดตัวละครสายป้องกันหรือรักษา (Preservation / Abundance) แนะนำใส่ 1 ตำแหน่งเพื่อความปลอดภัยในการเล่น</span>`;
|
||||
el.synergyAdviceBox.style.borderColor = 'rgba(245, 158, 11, 0.4)';
|
||||
} else if (roles.dps.length === 0) {
|
||||
el.synergyAdviceBox.innerHTML = `<span style="color: #38bdf8;">💡 <strong>ข้อแนะนำ:</strong> ทีมมีแต่ตัวซัพพอร์ต ยังไม่มีตัวทำดาเมจหลัก (Destruction / Hunt / Erudition)</span>`;
|
||||
el.synergyAdviceBox.style.borderColor = 'rgba(56, 189, 248, 0.4)';
|
||||
} else if (roles.support.length === 0) {
|
||||
el.synergyAdviceBox.innerHTML = `<span style="color: #fbbf24;">💡 <strong>ข้อแนะนำ:</strong> ทีมมีตัวดาเมจแต่ยังไม่มีตัวบัฟหรือดีบัฟ (Harmony / Nihility) เสริมตัวซัพพอร์ตจะช่วยเร่งดาเมจได้มหาศาล</span>`;
|
||||
el.synergyAdviceBox.style.borderColor = 'rgba(245, 158, 11, 0.4)';
|
||||
} else {
|
||||
el.synergyAdviceBox.innerHTML = `<span style="color: #34d399;">✅ <strong>สมดุลยอดเยี่ยม:</strong> ทีมมีโครงสร้างครบเครื่อง ทั้งตัวทำดาเมจ ซัพพอร์ตบัฟ/ดีบัฟ และตัวฮีลเลอร์/ชิลเดอร์ พร้อมลุยทุกคอนเทนต์!</span>`;
|
||||
el.synergyAdviceBox.style.borderColor = 'rgba(52, 211, 153, 0.4)';
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics
|
||||
if (el.synergyAvgLevel) el.synergyAvgLevel.textContent = activeIds.length ? `Lv.${avgLevel}` : '-';
|
||||
if (el.synergyAvgEidolon) el.synergyAvgEidolon.textContent = avgEidolon;
|
||||
if (el.synergyLcReady) el.synergyLcReady.textContent = activeIds.length ? `${lcCount}/${activeIds.length}` : '-';
|
||||
if (el.synergyRelicReady) el.synergyRelicReady.textContent = activeIds.length ? `${fullRelicCount}/${activeIds.length}` : '-';
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// GM Command Dispatcher & Live Log Console
|
||||
// ==========================================
|
||||
const consoleState = {
|
||||
history: [],
|
||||
historyIndex: -1,
|
||||
maxLogs: 200
|
||||
};
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (str === null || str === undefined) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function logToConsole(message, tag = 'SYS') {
|
||||
if (!el.consoleTerminalLog) return;
|
||||
const now = new Date();
|
||||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
|
||||
|
||||
let tagClass = 'tag-sys';
|
||||
const upperTag = String(tag).toUpperCase();
|
||||
if (upperTag === 'CMD' || upperTag === 'GM') tagClass = 'tag-cmd';
|
||||
else if (upperTag === 'OK' || upperTag === 'SUCCESS') tagClass = 'tag-ok';
|
||||
else if (upperTag === 'ERR' || upperTag === 'ERROR') tagClass = 'tag-err';
|
||||
else if (upperTag === 'NET') tagClass = 'tag-net';
|
||||
|
||||
const lineEl = document.createElement('div');
|
||||
lineEl.className = 'log-line';
|
||||
lineEl.innerHTML = `
|
||||
<span class="log-time">${timeStr}</span>
|
||||
<span class="log-tag ${tagClass}">[${escapeHtml(upperTag)}]</span>
|
||||
<span class="log-msg">${escapeHtml(message)}</span>
|
||||
`;
|
||||
|
||||
el.consoleTerminalLog.appendChild(lineEl);
|
||||
|
||||
// Trim excess logs if above maxLogs
|
||||
while (el.consoleTerminalLog.children.length > consoleState.maxLogs) {
|
||||
el.consoleTerminalLog.removeChild(el.consoleTerminalLog.firstChild);
|
||||
}
|
||||
|
||||
// Auto scroll to bottom
|
||||
el.consoleTerminalLog.scrollTop = el.consoleTerminalLog.scrollHeight;
|
||||
}
|
||||
|
||||
async function dispatchGMCommand(rawCmd) {
|
||||
const cmd = (rawCmd || '').trim();
|
||||
if (!cmd) return;
|
||||
|
||||
// Save to history
|
||||
if (consoleState.history[consoleState.history.length - 1] !== cmd) {
|
||||
consoleState.history.push(cmd);
|
||||
}
|
||||
consoleState.historyIndex = -1;
|
||||
|
||||
// Log user command
|
||||
logToConsole(cmd, 'CMD');
|
||||
|
||||
// Handle local /help command immediately
|
||||
if (cmd.toLowerCase() === '/help') {
|
||||
logToConsole('คำสั่ง GM ที่พร้อมใช้งาน (Available Commands):', 'SYS');
|
||||
logToConsole(' /heal - ฟื้นฟู HP/Energy เต็มตัวละครในทีมทั้งหมด', 'SYS');
|
||||
logToConsole(' /max - อัปตัวละครทั้งหมดเป็น Lv.80, E6, Traces 10', 'SYS');
|
||||
logToConsole(' /teleport <plane> [floor] - วาร์ปไปยังฉาก/Plane ID (เช่น 20001 รถไฟ Express)', 'SYS');
|
||||
logToConsole(' /scene <plane> [floor] - คำสั่งย่อของ /teleport', 'SYS');
|
||||
logToConsole(' /clear lineup - ล้างตัวละครในทีมปัจจุบันทั้งหมด', 'SYS');
|
||||
logToConsole(' /avatar <id> [lvl] [rank] - ปลดล็อกหรือตั้งค่าตัวละคร (เช่น /avatar 1308 80 6)', 'SYS');
|
||||
logToConsole(' /give <id> [count] - เพิ่มไอเทมหรือเงิน (เช่น 102 Jades, 2 Credits)', 'SYS');
|
||||
logToConsole(' /help - แสดงคู่มือรายการคำสั่งนี้', 'SYS');
|
||||
return;
|
||||
}
|
||||
|
||||
// If server is online, try dispatching to backend
|
||||
if (state.isConnected) {
|
||||
try {
|
||||
const res = await apiCall('/api/command', 'POST', { command: cmd });
|
||||
if (res) {
|
||||
const msg = res.message || res.msg || 'คำสั่งประมวลผลสำเร็จ';
|
||||
logToConsole(msg, 'OK');
|
||||
|
||||
// Command specific post-processing
|
||||
const lowerCmd = cmd.toLowerCase();
|
||||
if (lowerCmd.startsWith('/heal')) {
|
||||
showToast('ฟื้นฟู HP และ Energy เรียบร้อย', 'success');
|
||||
refreshStatus();
|
||||
} else if (lowerCmd.startsWith('/teleport') || lowerCmd.startsWith('/scene')) {
|
||||
showToast('ส่งคำสั่งเปลี่ยนฉากเรียบร้อย', 'success');
|
||||
refreshStatus();
|
||||
} else if (lowerCmd.startsWith('/clear lineup')) {
|
||||
showToast('ล้างตัวละครในทีมเรียบร้อย', 'info');
|
||||
refreshStatus();
|
||||
} else if (lowerCmd.startsWith('/max')) {
|
||||
showToast('ปรับ Max เลเวลและ Traces เรียบร้อย', 'success');
|
||||
refreshStatus();
|
||||
} else if (lowerCmd.startsWith('/give')) {
|
||||
showToast('เพิ่มไอเทม/เงินเรียบร้อย', 'success');
|
||||
} else if (lowerCmd.startsWith('/avatar')) {
|
||||
showToast('อัปเดตตัวละครเรียบร้อย', 'success');
|
||||
loadAvatars();
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
logToConsole(`Server Dispatcher: ${err.message} (เข้าสู่โหมด Local Fallback)`, 'NET');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to client-side local executions if server is offline or doesn't support the command
|
||||
const lowerCmd = cmd.toLowerCase();
|
||||
if (lowerCmd.startsWith('/heal')) {
|
||||
logToConsole('Local Fallback: ฟื้นฟู Energy ของตัวละครในทีมเป็น 100%', 'OK');
|
||||
if (state.fullFreesrData?.avatars) {
|
||||
for (const aid in state.fullFreesrData.avatars) {
|
||||
state.fullFreesrData.avatars[aid].sp_value = 140;
|
||||
}
|
||||
saveFreesrChangesToServer();
|
||||
}
|
||||
showToast('ฟื้นฟูพลังงานทีมเรียบร้อย', 'success');
|
||||
} else if (lowerCmd.startsWith('/max')) {
|
||||
logToConsole('Local Fallback: กำลังรัน maxAllAvatars()...', 'SYS');
|
||||
await maxAllAvatars();
|
||||
logToConsole('Local: อัปเลเวล 80 และ E6 ตัวละครทั้งหมดเรียบร้อย', 'OK');
|
||||
} else if (lowerCmd.startsWith('/clear lineup')) {
|
||||
logToConsole('Local Fallback: กำลังล้างทีม...', 'SYS');
|
||||
await quickClearLineup();
|
||||
logToConsole('Local: ล้างทีมเรียบร้อยแล้ว', 'OK');
|
||||
} else if (lowerCmd.startsWith('/teleport') || lowerCmd.startsWith('/scene')) {
|
||||
const parts = cmd.split(/\s+/);
|
||||
const planeId = parseInt(parts[1], 10) || 20001;
|
||||
const floorId = parseInt(parts[2], 10) || 1;
|
||||
logToConsole(`Local Fallback: ตั้งค่าฉากเป็น Plane ID ${planeId}, Floor ${floorId}`, 'OK');
|
||||
if (state.fullFreesrData) {
|
||||
state.fullFreesrData.scene = state.fullFreesrData.scene || {};
|
||||
state.fullFreesrData.scene.plane_id = planeId;
|
||||
state.fullFreesrData.scene.floor_id = floorId;
|
||||
saveFreesrChangesToServer();
|
||||
refreshStatus();
|
||||
}
|
||||
showToast(`เปลี่ยนฉากเป็น Plane ${planeId} เรียบร้อย`, 'success');
|
||||
} else if (lowerCmd.startsWith('/avatar')) {
|
||||
const parts = cmd.split(/\s+/);
|
||||
const aid = parseInt(parts[1], 10);
|
||||
const lvl = parseInt(parts[2], 10) || 80;
|
||||
const rank = parseInt(parts[3], 10) || 6;
|
||||
if (aid) {
|
||||
if (!state.fullFreesrData) state.fullFreesrData = { avatars: {}, lightcones: [], relics: [] };
|
||||
state.fullFreesrData.avatars = state.fullFreesrData.avatars || {};
|
||||
state.fullFreesrData.avatars[aid] = {
|
||||
owner_uid: 0,
|
||||
avatar_id: aid,
|
||||
level: lvl,
|
||||
promotion: lvl > 70 ? 6 : (lvl > 60 ? 5 : 4),
|
||||
data: { rank, skills: { 1: 10, 2: 10, 3: 10, 4: 10 } }
|
||||
};
|
||||
saveFreesrChangesToServer();
|
||||
loadAvatars();
|
||||
logToConsole(`Local: ปลดล็อก/อัปเดต Avatar ID ${aid} (Lv.${lvl}, E${rank}) เรียบร้อย`, 'OK');
|
||||
showToast(`อัปเดตตัวละคร ${aid} สำเร็จ`, 'success');
|
||||
} else {
|
||||
logToConsole('รูปแบบคำสั่งไม่ถูกต้อง: /avatar <id> [level] [rank]', 'ERR');
|
||||
}
|
||||
} else if (lowerCmd.startsWith('/give')) {
|
||||
const parts = cmd.split(/\s+/);
|
||||
const itemId = parseInt(parts[1], 10);
|
||||
const count = parseInt(parts[2], 10) || 1;
|
||||
if (itemId) {
|
||||
logToConsole(`Local: ได้รับ Item ID ${itemId} จำนวน ${count.toLocaleString()}`, 'OK');
|
||||
showToast(`เพิ่มไอเทม ${itemId} x${count}`, 'success');
|
||||
} else {
|
||||
logToConsole('รูปแบบคำสั่งไม่ถูกต้อง: /give <id> [count]', 'ERR');
|
||||
}
|
||||
} else {
|
||||
logToConsole(`คำสั่งที่ไม่รู้จักหรือไม่รองรับในโหมดออฟไลน์: "${cmd}" (พิมพ์ /help เพื่อดูคำสั่ง)`, 'ERR');
|
||||
}
|
||||
}
|
||||
|
||||
function setupGMConsoleEvents() {
|
||||
if (el.formConsoleCmd) {
|
||||
el.formConsoleCmd.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
if (!el.consoleCmdInput) return;
|
||||
const cmd = el.consoleCmdInput.value.trim();
|
||||
if (!cmd) return;
|
||||
el.consoleCmdInput.value = '';
|
||||
dispatchGMCommand(cmd);
|
||||
});
|
||||
}
|
||||
|
||||
// Key navigation in console input
|
||||
if (el.consoleCmdInput) {
|
||||
el.consoleCmdInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (consoleState.history.length === 0) return;
|
||||
if (consoleState.historyIndex === -1) {
|
||||
consoleState.historyIndex = consoleState.history.length - 1;
|
||||
} else if (consoleState.historyIndex > 0) {
|
||||
consoleState.historyIndex--;
|
||||
}
|
||||
el.consoleCmdInput.value = consoleState.history[consoleState.historyIndex] || '';
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (consoleState.history.length === 0 || consoleState.historyIndex === -1) return;
|
||||
if (consoleState.historyIndex < consoleState.history.length - 1) {
|
||||
consoleState.historyIndex++;
|
||||
el.consoleCmdInput.value = consoleState.history[consoleState.historyIndex];
|
||||
} else {
|
||||
consoleState.historyIndex = -1;
|
||||
el.consoleCmdInput.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Quick pill buttons
|
||||
document.querySelectorAll('.console-pill-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const cmd = btn.getAttribute('data-cmd');
|
||||
if (cmd) {
|
||||
if (el.consoleCmdInput) el.consoleCmdInput.value = cmd;
|
||||
dispatchGMCommand(cmd);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Copy Log button
|
||||
if (el.btnConsoleCopyLog) {
|
||||
el.btnConsoleCopyLog.addEventListener('click', () => {
|
||||
if (!el.consoleTerminalLog) return;
|
||||
const text = el.consoleTerminalLog.innerText || el.consoleTerminalLog.textContent;
|
||||
navigator.clipboard?.writeText(text).then(() => {
|
||||
showToast('คัดลอก Log ทั้งหมดลงในคลิปบอร์ดแล้ว', 'success');
|
||||
}).catch(() => {
|
||||
showToast('ไม่สามารถคัดลอก Log ได้', 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Clear Log button
|
||||
if (el.btnConsoleClearLog) {
|
||||
el.btnConsoleClearLog.addEventListener('click', () => {
|
||||
if (el.consoleTerminalLog) {
|
||||
el.consoleTerminalLog.innerHTML = '';
|
||||
logToConsole('ล้างหน้าจอ Terminal เรียบร้อย', 'SYS');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial greeting
|
||||
logToConsole('GaC Server Console พร้อมใช้งาน (พิมพ์ /help เพื่อดูคำสั่งทั้งหมด)', 'SYS');
|
||||
}
|
||||
|
||||
// Add extra slot in Creative Mode
|
||||
@@ -5908,6 +6337,7 @@ async function init() {
|
||||
setupModalSubtabs();
|
||||
setupEvents();
|
||||
setupGachabaseSyncEvents();
|
||||
setupGMConsoleEvents();
|
||||
applyTranslations();
|
||||
renderLineup(state.statusData?.lineups || DEFAULT_LINEUP);
|
||||
renderAvatarsGrid();
|
||||
|
||||
Reference in new issue
Block a user