Files
Gacy-Srtool/js/command-assistant.mjs

70 lines
7.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Command syntax mirrors the SDK dispatcher. Runtime IDs come from its read-only
// completion catalog; sample quantities are examples, not game resource IDs.
const arg = (name, help, source, optional = false) => ({ name, help, source, optional });
const quantity = (name = 'count') => arg(name, 'จำนวนเต็มบวก เช่น 1, 10 หรือ 100', ['1', '10', '100'], true);
const spec = (path, description, args = []) => ({ path: path.split(' '), description, args });
export const COMMANDS = [
spec('/help', 'แสดงรายการคำสั่งและวิธีใช้'),
spec('/heal', 'ส่งคำสั่งฟื้นฟู HP, พลังงาน และแต้มสกิล'),
spec('/max', 'ปรับตัวละครทั้งหมดเป็นเลเวล 80 และ E6'),
spec('/unstuck', 'กู้หน้าจอที่หมุนค้าง โดยเก็บความคืบหน้า CW'),
spec('/clear lineup', 'ล้างสมาชิกทีมปัจจุบัน'),
spec('/avatar', 'ปรับเลเวลและ Eidolon ของตัวละคร', [arg('id', 'เลือก ID ตัวละครจากรายชื่อ', 'avatars'), arg('level', 'เลเวล 1–80 (ไม่ใส่เพื่อคงค่าเดิม)', ['1','20','40','60','70','80'], true), arg('rank', 'Eidolon 0–6', ['0','1','2','3','4','5','6'], true)]),
...['/give','/item'].map(p => spec(p, 'ส่งคำสั่งเพิ่มไอเท็ม', [arg('item_id', 'ID ไอเท็มจาก catalog เซิร์ฟเวอร์', 'items'), quantity()])),
...['/teleport','/scene'].map(p => spec(p, 'เปลี่ยนฉากปลายทาง', [arg('plane_id', 'เลือก Plane ID', 'planes'), arg('floor_id', 'เลือกชั้นที่อยู่ใน Plane นี้', 'floors', true)])),
spec('/faketime', 'ดู/ตั้งเวลาเกม โดยไม่เปลี่ยนเวลา Windows', [arg('time', 'status = ดูเวลา · off = เวลาจริง · หรือ Unix seconds / RFC3339 พร้อม timezone', 'time', true)]),
spec('/cw help', 'แสดงวิธีใช้คำสั่ง CW'), spec('/cw status', 'ดูเงิน HP เลเวล และสถานะรอบ CW'),
spec('/cw list', 'แสดงรายการ ID ของ CW', [arg('category', 'เลือกชนิดข้อมูล', ['role','equip','orb','consumable','forge'])]),
...['gold','hp','exp'].map(p => spec('/cw '+p, p === 'exp' ? 'เพิ่ม EXP ในรอบ CW' : 'ตั้งค่า '+p+' ในรอบ CW', [arg(p === 'exp' ? 'gain' : 'total', p === 'hp' ? 'HP 1 ถึงเพดานของรอบปัจจุบัน' : 'จำนวนเต็มไม่ติดลบ (เป็นยอดรวม ยกเว้น exp ที่เป็นจำนวนเพิ่ม)', ['1','10','100','1000'])])),
...['tbpower','tb','trailblaze'].map(p => spec('/cw '+p, 'ตั้งค่าพลังบุกเบิก (Trailblaze Power / เหรียญร้านรถไฟ) ในรอบ CW', [arg('total', 'จำนวนพลังบุกเบิก เช่น 10, 50, 100', ['10','50','100','500'])])),
spec('/cw role', 'เพิ่มตัวละคร CW (ใช้ได้ใน Preparation)', [arg('roleID', 'เลือก Role ID ของ CW', 'cw_roles'), arg('star', 'ดาวที่มีข้อมูลรองรับสำหรับ Role นี้', 'cw_stars'), quantity()]),
...['equip','orb','consumable','forge'].map(p => spec('/cw '+p, 'เพิ่ม '+p+' ในรอบ CW (Preparation)', [arg('itemID', 'เลือก ID จากข้อมูล CW ของ Client', 'cw_'+p), quantity()])),
];
export function usage(command) {
return [...command.path, ...command.args.map(a => a.optional ? '['+a.name+']' : '<'+a.name+'>')].join(' ');
}
export function inspectCommand(value = '', caret = value.length, catalogs = {}) {
caret = Math.max(0, Math.min(caret, value.length));
const tokens = Array.from(value.matchAll(/\S+/g), m => ({ value:m[0], start:m.index, end:m.index+m[0].length }));
let index = tokens.findIndex(t => caret >= t.start && caret <= t.end);
if (index < 0) index = tokens.filter(t => t.end < caret).length;
const token = tokens[index];
const start = token?.start ?? caret, end = token?.end ?? caret;
const query = value.slice(start, caret).toLowerCase();
const words = tokens.map(t => t.value);
const matching = COMMANDS.filter(c => c.path.slice(0, Math.min(index, c.path.length)).every((p,i) => p.toLowerCase() === words[i]?.toLowerCase()));
const exact = COMMANDS.find(c => c.path.every((p,i) => p.toLowerCase() === words[i]?.toLowerCase()));
const command = value.trim() ? exact || matching.find(c => (c.path[index] || '').toLowerCase().startsWith(query)) || null : null;
const suggestions = [];
for (const c of matching) if (index < c.path.length && c.path[index].toLowerCase().startsWith(query)) {
suggestions.push({ value:c.path[index], label:c.path[index], description:c.description });
}
const argIndex = exact ? index-exact.path.length : -1;
const active = exact?.args[argIndex];
if (active) {
let choices = Array.isArray(active.source) ? active.source : catalogs[active.source] || [];
if (active.source === 'time') choices = ['status','off','reset','real',new Date().toISOString().replace(/\.\d{3}Z$/, 'Z')];
if (active.source === 'floors') choices = (catalogs.floors || []).filter(c => String(c.plane) === words[exact.path.length]);
if (active.source === 'cw_stars') choices = (catalogs.cw_roles || []).filter(c => String(c.value) === words[exact.path.length]).map(c => String(c.star));
for (const entry of choices) {
const choice = typeof entry === 'object' ? entry : {value:String(entry),label:String(entry)};
if ((choice.value+' '+(choice.label || '')).toLowerCase().includes(query)) suggestions.push({...choice, description:active.help});
}
}
const seen = new Set();
const unique = suggestions.filter(s => { if(seen.has(s.value))return false;seen.add(s.value);return true; });
const missing = exact ? exact.args.filter((a,i) => !a.optional && !words[exact.path.length+i]).map(a => a.name) : [];
return { command, active, argIndex, index, start, end, suggestions:unique, missing,
preview:command ? [...command.path, ...command.args.map((a,i) => words[command.path.length+i] || (a.optional ? '['+a.name+']' : '<'+a.name+'>'))].join(' ') : value.trim() ? 'ไม่พบคำสั่งนี้ในรายการ — ตรวจชื่อคำสั่งก่อนส่ง' : 'พิมพ์ / เพื่อเลือกคำสั่ง',
hint:active ? `${active.optional ? 'ช่องเสริม' : 'ช่องที่ต้องใส่'}: ${active.name} — ${active.help}` : exact ? (missing.length ? 'ยังขาด: '+missing.join(', ') : 'ใส่ครบแล้ว · Enter หรือ Run เพื่อส่งคำสั่ง') : command && index > 0 ? 'ช่องถัดไป: เลือกคำสั่งย่อยจากรายการด้านล่าง' : '↑ ↓ เลื่อน · Tab เติมค่าที่เลือก · Esc ปิดรายการ',
};
}
export function completeCommand(value, model, choice) {
const suffix = value.slice(model.end);
const separator = /^\s/.test(suffix) ? '' : ' ';
return { value:value.slice(0,model.start)+choice.value+separator+suffix, caret:model.start+choice.value.length+1 };
}