commit 33278969ec2712e21759c15333e979d7161fba62 Author: WildEgo Date: Sat Jun 21 00:30:58 2025 +0100 Initial commit (special_item_group support) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d72b4fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,178 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +input/ +output/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..66624f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ab6283 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# json_transform + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.1.43. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..137450e --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "indentWidth": 2, + "indentStyle": "space" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..d550b28 Binary files /dev/null and b/bun.lockb differ diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f2abf0a --- /dev/null +++ b/index.ts @@ -0,0 +1,146 @@ +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', + }, + ], +}); + +if (isCancel(fileType)) { + cancel('Operation cancelled'); + process.exit(0); +} + +await match(fileType) + .with('special_item_group', async () => { + const s = spinner(); + + const proto = csvToJson.fieldDelimiter('\t').csvStringToJson(itemProto); + + s.start('Transforming'); + + 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, + ), + ); + }) + .otherwise(() => { + outro(color.bgRed('Invalid file type')); + process.exit(1); + }); + +outro("You're all set now"); diff --git a/package.json b/package.json new file mode 100644 index 0000000..1c9ea29 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "json_transform", + "module": "transform.ts", + "type": "module", + "devDependencies": { + "@biomejs/biome": "2.0.0", + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@clack/prompts": "^0.11.0", + "convert-csv-to-json": "^3.5.0", + "ts-pattern": "^5.7.1" + } +} \ No newline at end of file diff --git a/schemas/special_item_group.json b/schemas/special_item_group.json new file mode 100644 index 0000000..1afbcad --- /dev/null +++ b/schemas/special_item_group.json @@ -0,0 +1,111 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://localhost:8080/$schemas/special_item_group.json", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "type", "vnum", "rows"], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["percentage", "attribute", "quest", "special", "normal"] + }, + "vnum": { + "type": "number" + }, + "effect": { + "type": ["string", "null"] + }, + "rows": { + "type": "array" + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "attribute" + } + } + }, + "then": { + "properties": { + "rows": { + "items": { + "type": "object", + "required": ["apply_type", "apply_value"], + "properties": { + "apply_type": { + "type": "number" + }, + "apply_value": { + "type": "number" + } + }, + "additionalProperties": false + } + } + } + }, + "else": { + "properties": { + "rows": { + "items": { + "type": "object", + "required": [ + "vnum", + "count", + "probability", + "rare_percentage" + ], + "properties": { + "vnum": { + "oneOf": [ + { + "type": "string", + "enum": [ + "exp", + "mob", + "slow", + "drain_hp", + "poison", + "group" + ] + }, + { + "type": "number" + } + ] + }, + "count": { + "type": "number" + }, + "probability": { + "type": "number" + }, + "rare_percentage": { + "type": "number" + } + }, + "additionalProperties": false + } + } + } + } + } + ] + } + } + }, + "required": ["$schema", "rows"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}