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";
|
} from "../services/editor.service.js";
|
||||||
import { lookupResref, getResrefCount } from "../nwscript/resref-index.js";
|
import { lookupResref, getResrefCount } from "../nwscript/resref-index.js";
|
||||||
import { lookupTlk, getTlkCount } from "../nwscript/tlk-index.js";
|
import { lookupTlk, getTlkCount } from "../nwscript/tlk-index.js";
|
||||||
|
import { get2DAFile, list2DAFiles, get2DARow } from "../nwscript/twoda-index.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -78,4 +79,22 @@ router.get("/tlk-count", (_req, res) => {
|
|||||||
res.json({ count: getTlkCount() });
|
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;
|
export default router;
|
||||||
|
|||||||
Reference in New Issue
Block a user