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 +1 -0
- package/package.json +3 -3
- package/src/LevelDict.ts +21 -27
- package/src/__tests__/LevelFile.test.ts +24 -13
- package/src/types/index.ts +1 -1
- package/src/types/level.ts +19 -0
- package/src/utils/levelUtils.ts +18 -0
- package/tsconfig.json +3 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adofai-lib",
|
|
3
|
-
"version": "1.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
|
|
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
|
|
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 (!
|
|
72
|
-
return fs.readFileSync(
|
|
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():
|
|
76
|
-
if (!
|
|
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
|
|
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
|
-
|
|
93
|
+
while (angles.length > this.tiles.length) {
|
|
95
94
|
this.tiles.push({
|
|
96
|
-
angle: angles[
|
|
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()
|
|
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
|
-
|
|
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.
|
|
156
|
+
angleData: this.getAngles(),
|
|
160
157
|
settings: { ...this.settings },
|
|
161
|
-
actions: this.
|
|
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 {
|
|
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('
|
|
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
|
|
15
|
-
expect(level.getAngles()).toEqual([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
|
|
19
|
+
it('should throw InvalidLevelException for invalid file', () => {
|
|
21
20
|
(fs.readFileSync as jest.Mock).mockReturnValue('{}');
|
|
22
21
|
|
|
23
22
|
expect(() => {
|
|
24
|
-
new
|
|
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
|
|
39
|
-
expect(level.getAngles()).toEqual([0, 90, 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
|
|
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
|
|
66
|
-
expect(level.getAngles()).toEqual([0, 90, 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
|
|
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
|
});
|
package/src/types/index.ts
CHANGED
|
@@ -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"]
|