From 33278969ec2712e21759c15333e979d7161fba62 Mon Sep 17 00:00:00 2001 From: WildEgo Date: Sat, 21 Jun 2025 00:30:58 +0100 Subject: [PATCH] Initial commit (special_item_group support) --- .gitignore | 178 ++++++++++++++++++++++++++++++++ .vscode/settings.json | 5 + README.md | 15 +++ biome.json | 36 +++++++ bun.lockb | Bin 0 -> 8490 bytes index.ts | 146 ++++++++++++++++++++++++++ package.json | 17 +++ schemas/special_item_group.json | 111 ++++++++++++++++++++ tsconfig.json | 27 +++++ 9 files changed, 535 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 biome.json create mode 100755 bun.lockb create mode 100644 index.ts create mode 100644 package.json create mode 100644 schemas/special_item_group.json create mode 100644 tsconfig.json 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 0000000000000000000000000000000000000000..d550b28acdf8a7ce9b266f0f4afa0bec54ede950 GIT binary patch literal 8490 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 I{q_F-4_PCusQ>@~ literal 0 HcmV?d00001 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 + } +}