206 lines
5.5 KiB
TypeScript
206 lines
5.5 KiB
TypeScript
import {
|
|
intro,
|
|
outro,
|
|
select,
|
|
spinner,
|
|
isCancel,
|
|
cancel,
|
|
} from '@clack/prompts';
|
|
import color from 'picocolors';
|
|
import { match, P } from 'ts-pattern';
|
|
import csvToJson from 'convert-csv-to-json';
|
|
import itemProto from './input/item_proto.txt' with { type: 'text' };
|
|
|
|
if (!itemProto) {
|
|
throw new Error('The file input/item_proto.txt was not found');
|
|
}
|
|
|
|
intro(color.inverse('JSON Transform'));
|
|
|
|
const fileType = await select({
|
|
message: 'Pick a file to transform',
|
|
options: [
|
|
{
|
|
value: 'special_item_group',
|
|
label: 'special_item_group.txt',
|
|
},
|
|
{
|
|
value: 'blend',
|
|
label: 'blend.txt',
|
|
},
|
|
],
|
|
});
|
|
|
|
const getProto = () =>
|
|
csvToJson.fieldDelimiter('\t').csvStringToJson(itemProto);
|
|
|
|
if (isCancel(fileType)) {
|
|
cancel('Operation cancelled');
|
|
process.exit(0);
|
|
}
|
|
|
|
await match(fileType)
|
|
.with('special_item_group', async () => {
|
|
const s = spinner();
|
|
|
|
const proto = getProto();
|
|
|
|
s.start('Transforming special_item_group.txt');
|
|
|
|
const input = Bun.file('./input/special_item_group.txt');
|
|
if (!input.exists()) {
|
|
throw new Error('The file input/special_item_group.txt was not found');
|
|
}
|
|
|
|
const specialItemGroup = await input.text();
|
|
|
|
const REGEX =
|
|
/group\s+([^\n]+)\s*\{\s*vnum\s+([^\n]+)\s*(?:type\s+([^\n]+)\s*)?((?:\d+\s+[^\n]+\s*)+)\s*(?:effect\s+([^\n]+)\s*)?}/gim;
|
|
|
|
const matches = Array.from(specialItemGroup.matchAll(REGEX)).map(
|
|
(instance) => {
|
|
const [, name, vnum, baseType, rowsBlock, effect] = instance;
|
|
|
|
const type = match(baseType?.trim().toLocaleLowerCase())
|
|
.with('pct', () => 'percentage' as const)
|
|
.with('attr', () => 'attribute' as const)
|
|
.with('quest', () => 'quest' as const)
|
|
.with('special', () => 'special' as const)
|
|
.otherwise(() => 'normal' as const);
|
|
|
|
const STANDARD_ROW_REGEX =
|
|
/^[ \t]*\d+\s+(\S+)\s+(\d+)\s+(\d+)(?:\s+(\S+))?/gm;
|
|
const ATTR_ROW_REGEX = /^[ \t]*\d+\s+(\d+)\s+(\d+)?/gm;
|
|
|
|
const rows = Array.from(
|
|
rowsBlock.matchAll(
|
|
type === 'attribute' ? ATTR_ROW_REGEX : STANDARD_ROW_REGEX,
|
|
),
|
|
).map((row) => {
|
|
const value1 = Number(row[2]);
|
|
const value2 = Number(row[3]);
|
|
const value3 = Number(row[4] || 0);
|
|
|
|
return match(type)
|
|
.with('attribute', () => ({
|
|
apply_type: Number(row[1]),
|
|
apply_value: value1,
|
|
}))
|
|
.otherwise(() => ({
|
|
vnum: match(row[1].trim())
|
|
.with(P.union('경험치', 'exp'), () => 'exp')
|
|
.with(
|
|
P.union('mob', 'slow', 'drain_hp', 'poison', 'group'),
|
|
(v) => v,
|
|
)
|
|
.when(
|
|
(v) => Number.isNaN(Number(v)),
|
|
(v) => {
|
|
const itemMatch = proto.find(
|
|
(item) => item['ITEM_NAME(K)'] === v,
|
|
);
|
|
|
|
if (!itemMatch) {
|
|
throw new Error(`Item not found ${v}`);
|
|
}
|
|
|
|
return Number(itemMatch['ITEM번호']);
|
|
},
|
|
)
|
|
.otherwise((value) => Number(value)),
|
|
count: value1,
|
|
probability: value2,
|
|
rare_percentage: value3,
|
|
}));
|
|
});
|
|
|
|
return {
|
|
name: name.trim(),
|
|
type,
|
|
vnum: Number(vnum),
|
|
rows,
|
|
effect: effect
|
|
? effect.trim().substring(1, effect.trim().length - 1)
|
|
: null,
|
|
};
|
|
},
|
|
);
|
|
|
|
s.stop('special_item_group.txt was transformed');
|
|
|
|
const out = Bun.file('./output/special_item_group.json');
|
|
if (await out.exists()) {
|
|
await out.delete();
|
|
}
|
|
|
|
out.write(
|
|
JSON.stringify(
|
|
{
|
|
$schema:
|
|
'https://raw.githubusercontent.com/WildEgo/m2-json-schemas/refs/heads/main/special_item_group.json',
|
|
rows: matches,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
})
|
|
.with('blend', async () => {
|
|
const s = spinner();
|
|
|
|
s.start('Transforming blend.txt');
|
|
|
|
const input = Bun.file('./input/blend.txt');
|
|
if (!input.exists()) {
|
|
throw new Error('The file input/special_item_group.txt was not found');
|
|
}
|
|
|
|
const blend = await input.text();
|
|
|
|
const REGEX =
|
|
/section\s+[\s\S]*?item_vnum\s+(\d+)[\s\S]*?apply_type\s+(\S+)[\s\S]*?apply_value\s+([\d\s]+)[\s\S]*?apply_duration\s+([\d\s]+)[\s\S]*?end/gim;
|
|
|
|
const matches = Array.from(blend.matchAll(REGEX)).map(
|
|
([_, vnum, apply_type, values, durations]) => {
|
|
const validDurations = durations.trim().split(/\t/);
|
|
|
|
return {
|
|
vnum: parseInt(vnum, 10),
|
|
apply_type,
|
|
apply_data: values
|
|
.trim()
|
|
.split(/\t/)
|
|
.map((value, index) => ({
|
|
value: parseInt(value, 10),
|
|
duration: parseInt(validDurations[index], 10),
|
|
})),
|
|
};
|
|
},
|
|
);
|
|
|
|
s.stop('blend.txt was transformed');
|
|
|
|
const out = Bun.file('./output/blend.json');
|
|
if (await out.exists()) {
|
|
await out.delete();
|
|
}
|
|
|
|
out.write(
|
|
JSON.stringify(
|
|
{
|
|
$schema:
|
|
'https://raw.githubusercontent.com/WildEgo/m2-json-schemas/refs/heads/main/blend.json',
|
|
rows: matches,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
})
|
|
.otherwise(() => {
|
|
outro(color.bgRed('Invalid file type'));
|
|
process.exit(1);
|
|
});
|
|
|
|
outro("You're all set now");
|