adofai-lib 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # ADOFAI Library
2
2
 
3
3
  A TypeScript library for working with ADOFAI level files. This library provides a simple interface for reading, modifying, and writing ADOFAI level files.
4
+ Ported from M1n3c4rt's `adofaipy` lib and expanded by V0W4N
4
5
 
5
6
  ## Installation
6
7
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adofai-lib",
3
- "version": "1.0.0",
4
- "description": "A TypeScript library for working with ADOFAI level files",
3
+ "version": "1.0.1",
4
+ "description": "A TypeScript library for working with ADOFAI level files. Original by M1n3c4rt",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
@@ -15,7 +15,7 @@
15
15
  "adofai-lib-js",
16
16
  "adofai-lib-js-v0w4n"
17
17
  ],
18
- "author": "V0W4N (original by M1n3c4rt)",
18
+ "author": "V0W4N",
19
19
  "license": "MIT",
20
20
  "devDependencies": {
21
21
  "@types/jest": "^29.5.12",
package/src/LevelDict.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as fs from 'fs';
2
+ import * as utils from './utils/levelUtils';
2
3
  import { Action, Decoration, Settings, Tile, InvalidLevelDictException } from './types';
4
+ import { LevelJSON } from './types/level';
3
5
 
4
- export class LevelFile {
6
+ export class LevelDict {
5
7
  private filename: string;
6
8
  private encoding: BufferEncoding;
7
9
  private tiles: Tile[];
@@ -15,7 +17,7 @@ export class LevelFile {
15
17
  this.nonFloorDecos = [];
16
18
  this.settings = {};
17
19
 
18
- const leveldict = this.getFileDict();
20
+ const leveldict = this.getFileDict(filename);
19
21
  if (!leveldict.actions || !leveldict.settings || (!leveldict.angleData && !leveldict.pathData)) {
20
22
  throw new InvalidLevelDictException(`The provided .adofai file is invalid. (missing: ${!leveldict.actions ? 'actions' : ''} ${!leveldict.settings ? 'settings' : ''} ${!leveldict.angleData && !leveldict.pathData ? 'angleData or pathData' : ''})`);
21
23
  }
@@ -32,9 +34,6 @@ export class LevelFile {
32
34
  };
33
35
  angleData = Array.from(leveldict.pathData as string).map(char => pathchars[char]);
34
36
  }
35
-
36
- angleData.push(angleData[angleData.length - 1] !== 999 ? angleData[angleData.length - 1] : (angleData[angleData.length - 2] + 180) % 360);
37
-
38
37
  const actions = leveldict.actions;
39
38
  const decorations = leveldict.decorations || [];
40
39
 
@@ -47,7 +46,7 @@ export class LevelFile {
47
46
  actions: [],
48
47
  decorations: []
49
48
  }));
50
-
49
+ console.log('mapping tiles', angleData, this.tiles);
51
50
  // Add actions
52
51
  actions.forEach((action: Action) => {
53
52
  if (action.floor !== undefined && action.floor < this.tiles.length) {
@@ -67,13 +66,13 @@ export class LevelFile {
67
66
  });
68
67
  }
69
68
 
70
- private getFileString(): string {
71
- if (!this.filename) return '';
72
- return fs.readFileSync(this.filename, { encoding: this.encoding });
69
+ private getFileString(filename: string): string {
70
+ if (!filename) return '';
71
+ return fs.readFileSync(filename, { encoding: this.encoding });
73
72
  }
74
73
 
75
- private getFileDict(): any {
76
- if (!this.filename) {
74
+ private getFileDict(filename: string): LevelJSON {
75
+ if (!filename) {
77
76
  return {
78
77
  angleData: [0],
79
78
  settings: {},
@@ -82,8 +81,8 @@ export class LevelFile {
82
81
  };
83
82
  }
84
83
 
85
- const content = this.getFileString();
86
- return JSON.parse(content);
84
+ const content = this.getFileString(filename);
85
+ return utils.safeParseJSON(content);
87
86
  }
88
87
 
89
88
  public getAngles(): number[] {
@@ -91,9 +90,9 @@ export class LevelFile {
91
90
  }
92
91
 
93
92
  public setAngles(angles: number[]): void {
94
- if (angles.length > this.tiles.length) {
93
+ while (angles.length > this.tiles.length) {
95
94
  this.tiles.push({
96
- angle: angles[angles.length - 1],
95
+ angle: angles[this.tiles.length],
97
96
  actions: [],
98
97
  decorations: []
99
98
  });
@@ -105,7 +104,7 @@ export class LevelFile {
105
104
  }
106
105
 
107
106
  public getAnglesRelative(ignoreTwirls: boolean = false, padMidspins: boolean = false): number[] {
108
- const absangles = this.getAngles().slice(0, -1);
107
+ const absangles = this.getAngles();
109
108
 
110
109
  if (!ignoreTwirls) {
111
110
  const twirls = this.getActions(action => action.eventType === 'Twirl')
@@ -148,23 +147,18 @@ export class LevelFile {
148
147
  }
149
148
 
150
149
  public getDecorations(condition: (decoration: Decoration) => boolean = () => true): Decoration[] {
151
- return [
152
- ...this.tiles.flatMap(tile => tile.decorations.filter(condition)),
153
- ...this.nonFloorDecos.filter(condition)
154
- ];
150
+ return this.tiles.flatMap(tile => tile.decorations.filter(condition))
151
+ .concat(this.nonFloorDecos.filter(condition));
155
152
  }
156
153
 
157
154
  public writeToFile(filename?: string): void {
158
155
  const final = {
159
- angleData: this.tiles.map(tile => tile.angle),
156
+ angleData: this.getAngles(),
160
157
  settings: { ...this.settings },
161
- actions: this.tiles.flatMap(tile => tile.actions),
162
- decorations: [
163
- ...this.tiles.flatMap(tile => tile.decorations),
164
- ...this.nonFloorDecos
165
- ]
158
+ actions: this.getActions(),
159
+ decorations: this.getDecorations()
166
160
  };
167
-
161
+ console.log(final);
168
162
  const outputFile = filename || this.filename;
169
163
  fs.writeFileSync(outputFile, JSON.stringify(final, null, 4), { encoding: this.encoding });
170
164
  }
@@ -1,27 +1,26 @@
1
- import { LevelFile } from '../LevelDict';
1
+ import { LevelDict } from '../LevelDict';
2
2
  import * as fs from 'fs';
3
- import * as path from 'path';
4
3
 
5
4
  // Mock fs module
6
5
  jest.mock('fs');
7
6
 
8
- describe('LevelFile', () => {
7
+ describe('LevelDict', () => {
9
8
  beforeEach(() => {
10
9
  jest.clearAllMocks();
11
10
  });
12
11
 
13
12
  it('should create an empty level file when no filename is provided', () => {
14
- const level = new LevelFile();
15
- expect(level.getAngles()).toEqual([0,0]);
13
+ const level = new LevelDict();
14
+ expect(level.getAngles()).toEqual([0]);
16
15
  expect(level.getActions()).toEqual([]);
17
16
  expect(level.getDecorations()).toEqual([]);
18
17
  });
19
18
 
20
- it('should throw InvalidLevelDictException for invalid file', () => {
19
+ it('should throw InvalidLevelException for invalid file', () => {
21
20
  (fs.readFileSync as jest.Mock).mockReturnValue('{}');
22
21
 
23
22
  expect(() => {
24
- new LevelFile('invalid.adofai');
23
+ new LevelDict('invalid.adofai');
25
24
  }).toThrow('The provided .adofai file is invalid');
26
25
  });
27
26
 
@@ -35,12 +34,12 @@ describe('LevelFile', () => {
35
34
 
36
35
  (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockLevelData));
37
36
 
38
- const level = new LevelFile('test.adofai');
39
- expect(level.getAngles()).toEqual([0, 90, 180, 180]);
37
+ const level = new LevelDict('test.adofai');
38
+ expect(level.getAngles()).toEqual([0, 90, 180]);
40
39
  });
41
40
 
42
41
  it('should write level file correctly', () => {
43
- const level = new LevelFile();
42
+ const level = new LevelDict();
44
43
  level.setAngles([0, 90, 180]);
45
44
 
46
45
  level.writeToFile('output.adofai');
@@ -62,8 +61,8 @@ describe('LevelFile', () => {
62
61
 
63
62
  (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockLevelData));
64
63
 
65
- const level = new LevelFile('test.adofai');
66
- expect(level.getAngles()).toEqual([0, 90, 180, 180]);
64
+ const level = new LevelDict('test.adofai');
65
+ expect(level.getAngles()).toEqual([0, 90, 180]);
67
66
  });
68
67
 
69
68
  it('should get relative angles correctly', () => {
@@ -76,8 +75,20 @@ describe('LevelFile', () => {
76
75
 
77
76
  (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockLevelData));
78
77
 
79
- const level = new LevelFile('test.adofai');
78
+ const level = new LevelDict('test.adofai');
80
79
  const relativeAngles = level.getAnglesRelative();
81
80
  expect(relativeAngles).toEqual([180, 90, 90, 180]);
82
81
  });
82
+
83
+ it('should parse malformed parameter sequence', () => {
84
+ const mockBadLevelData = `{
85
+ "pathData": "RURU",
86
+ "settings": { "bpm": 120 },
87
+ "actions": []
88
+ "decorations": []
89
+ }`;
90
+ (fs.readFileSync as jest.Mock).mockReturnValue(mockBadLevelData);
91
+ const level = new LevelDict('test.adofai');
92
+ expect(level.getAngles()).toEqual([0, 90, 0, 90]);
93
+ });
83
94
  });
@@ -11,7 +11,7 @@ export interface Decoration {
11
11
  }
12
12
 
13
13
  export interface Settings {
14
- [key: string]: any;
14
+ [key: string]: any;
15
15
  }
16
16
 
17
17
  export interface Tile {
@@ -0,0 +1,19 @@
1
+ import { Settings } from ".";
2
+
3
+ export interface LevelJSON {
4
+ actions?: Array<Record<string, any>>;
5
+ decorations?: Array<Record<string, any>>;
6
+ settings?: Settings;
7
+ [key: string]: any;
8
+ }
9
+
10
+ export interface TransformOptions {
11
+ keepEventTypes?: Set<string>;
12
+ dropEventTypes?: Set<string>;
13
+ baseCameraZoom?: number;
14
+ extraProtectedEventTypes?: Set<string>;
15
+ additionalPatterns?: Set<RegExp>;
16
+ constantBackgroundColor?: string;
17
+ removeForegroundFlash?: boolean;
18
+ dropFilters?: Set<string>;
19
+ }
@@ -0,0 +1,18 @@
1
+ import JSON5 from 'json5';
2
+ import { LevelJSON } from '../types/level';
3
+
4
+ /**
5
+ * Ensure bad comma handling
6
+ */
7
+ export function safeParseJSON(str: string): LevelJSON {
8
+ try {
9
+ const cleaned = str
10
+ .replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas before closing brackets
11
+ .replace(/([}\]])[\s\n]*([}\]])/g, '$1,$2') // Ensure comma between closing brackets
12
+ .replace(/([}\]])[\s\n]*("decorations"|"actions"|"settings")/g, '$1,$2'); // Ensure comma before main sections
13
+
14
+ return JSON5.parse(cleaned);
15
+ } catch (e) {
16
+ throw new Error('Failed to parse level JSON');
17
+ }
18
+ }
package/tsconfig.json CHANGED
@@ -4,12 +4,14 @@
4
4
  "module": "commonjs",
5
5
  "declaration": true,
6
6
  "outDir": "./dist",
7
+ "rootDir": "./src",
7
8
  "strict": true,
8
9
  "esModuleInterop": true,
9
10
  "skipLibCheck": true,
10
11
  "forceConsistentCasingInFileNames": true,
11
12
  "moduleResolution": "node",
12
- "lib": ["es2019", "dom"]
13
+ "lib": ["es2019", "dom"],
14
+ "composite": false
13
15
  },
14
16
  "include": ["src/**/*"],
15
17
  "exclude": ["node_modules", "dist", "**/*.test.ts"]