feat: add 2DA intellisense parser and lookup for NWScript editor
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
interface TwoDAFile {
|
||||
columns: string[];
|
||||
rows: Map<number, Map<string, string>>;
|
||||
}
|
||||
|
||||
const files = new Map<string, TwoDAFile>();
|
||||
|
||||
export async function loadTwoDAFiles(twodaDir: string): Promise<void> {
|
||||
files.clear();
|
||||
let entries;
|
||||
try {
|
||||
entries = await fs.readdir(twodaDir);
|
||||
} catch {
|
||||
return; // directory doesn't exist yet
|
||||
}
|
||||
|
||||
for (const filename of entries) {
|
||||
if (!filename.endsWith(".2da")) continue;
|
||||
try {
|
||||
const content = await fs.readFile(path.join(twodaDir, filename), "utf-8");
|
||||
const parsed = parse2DA(content);
|
||||
if (parsed) {
|
||||
files.set(filename.replace(".2da", ""), parsed);
|
||||
}
|
||||
} catch {
|
||||
// skip unparseable files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parse2DA(content: string): TwoDAFile | null {
|
||||
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
||||
if (lines.length < 3) return null;
|
||||
|
||||
let headerLineIdx = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].trim().startsWith("2DA")) continue;
|
||||
if (lines[i].trim() === "") continue;
|
||||
headerLineIdx = i;
|
||||
break;
|
||||
}
|
||||
if (headerLineIdx === -1) return null;
|
||||
|
||||
const columns = lines[headerLineIdx].trim().split(/\s+/);
|
||||
const rows = new Map<number, Map<string, string>>();
|
||||
|
||||
for (let i = headerLineIdx + 1; i < lines.length; i++) {
|
||||
const parts = lines[i].trim().split(/\s+/);
|
||||
if (parts.length < 2) continue;
|
||||
const rowIndex = parseInt(parts[0], 10);
|
||||
if (isNaN(rowIndex)) continue;
|
||||
const rowData = new Map<string, string>();
|
||||
for (let c = 0; c < columns.length; c++) {
|
||||
const val = parts[c + 1] || "****";
|
||||
rowData.set(columns[c], val);
|
||||
}
|
||||
rows.set(rowIndex, rowData);
|
||||
}
|
||||
|
||||
return { columns, rows };
|
||||
}
|
||||
|
||||
export function get2DAFile(name: string): TwoDAFile | undefined {
|
||||
return files.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
export function list2DAFiles(): string[] {
|
||||
return Array.from(files.keys());
|
||||
}
|
||||
|
||||
export function get2DARow(name: string, row: number): Record<string, string> | undefined {
|
||||
const file = files.get(name.toLowerCase());
|
||||
if (!file) return undefined;
|
||||
const rowData = file.rows.get(row);
|
||||
if (!rowData) return undefined;
|
||||
return Object.fromEntries(rowData);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "../services/editor.service.js";
|
||||
import { lookupResref, getResrefCount } from "../nwscript/resref-index.js";
|
||||
import { lookupTlk, getTlkCount } from "../nwscript/tlk-index.js";
|
||||
import { get2DAFile, list2DAFiles, get2DARow } from "../nwscript/twoda-index.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -78,4 +79,22 @@ router.get("/tlk-count", (_req, res) => {
|
||||
res.json({ count: getTlkCount() });
|
||||
});
|
||||
|
||||
router.get("/2da", (_req, res) => {
|
||||
res.json({ files: list2DAFiles() });
|
||||
});
|
||||
|
||||
router.get("/2da/:name", (req, res) => {
|
||||
const file = get2DAFile(req.params.name);
|
||||
if (!file) return res.status(404).json({ error: "2DA file not found" });
|
||||
res.json({ columns: file.columns, rowCount: file.rows.size });
|
||||
});
|
||||
|
||||
router.get("/2da/:name/:row", (req, res) => {
|
||||
const row = parseInt(req.params.row, 10);
|
||||
if (isNaN(row)) return res.status(400).json({ error: "invalid row" });
|
||||
const data = get2DARow(req.params.name, row);
|
||||
if (!data) return res.status(404).json({ error: "row not found" });
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user