adofai-lib 1.0.0 → 1.0.2

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
 
@@ -0,0 +1,17 @@
1
+ import { Action, Decoration } from './types';
2
+ export declare class Level {
3
+ private filename;
4
+ private encoding;
5
+ private tiles;
6
+ private nonFloorDecos;
7
+ private settings;
8
+ constructor(filename?: string, encoding?: BufferEncoding);
9
+ private getFileString;
10
+ private getFileDict;
11
+ getAngles(): number[];
12
+ setAngles(angles: number[]): void;
13
+ getAnglesRelative(ignoreTwirls?: boolean, padMidspins?: boolean): number[];
14
+ getActions(condition?: (action: Action) => boolean): Action[];
15
+ getDecorations(condition?: (decoration: Decoration) => boolean): Decoration[];
16
+ writeToFile(filename?: string): void;
17
+ }
package/dist/Level.js ADDED
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Level = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const types_1 = require("./types");
39
+ class Level {
40
+ constructor(filename = '', encoding = 'utf-8') {
41
+ this.filename = filename;
42
+ this.encoding = encoding;
43
+ this.tiles = [];
44
+ this.nonFloorDecos = [];
45
+ this.settings = {};
46
+ const leveldict = this.getFileDict();
47
+ if (!leveldict.actions || !leveldict.settings || (!leveldict.angleData && !leveldict.pathData)) {
48
+ throw new types_1.InvalidLevelException(`The provided .adofai file is invalid. (missing: ${!leveldict.actions ? 'actions' : ''} ${!leveldict.settings ? 'settings' : ''} ${!leveldict.angleData && !leveldict.pathData ? 'angleData or pathData' : ''})`);
49
+ }
50
+ let angleData;
51
+ if ('angleData' in leveldict) {
52
+ angleData = leveldict.angleData;
53
+ }
54
+ else {
55
+ const pathchars = {
56
+ 'R': 0, 'p': 15, 'J': 30, 'E': 45, 'T': 60, 'o': 75, 'U': 90,
57
+ 'q': 105, 'G': 120, 'Q': 135, 'H': 150, 'W': 165, 'L': 180,
58
+ 'x': 195, 'N': 210, 'Z': 225, 'F': 240, 'V': 255, 'D': 270,
59
+ 'Y': 285, 'B': 300, 'C': 315, 'M': 330, 'A': 345, '!': 999
60
+ };
61
+ angleData = Array.from(leveldict.pathData).map(char => pathchars[char]);
62
+ }
63
+ angleData.push(angleData[angleData.length - 1] !== 999 ? angleData[angleData.length - 1] : (angleData[angleData.length - 2] + 180) % 360);
64
+ const actions = leveldict.actions;
65
+ const decorations = leveldict.decorations || [];
66
+ this.nonFloorDecos = decorations.filter((j) => !('floor' in j)).map((j) => ({ ...j }));
67
+ this.settings = filename !== '' ? { ...leveldict.settings } : {};
68
+ // Initialize tiles
69
+ this.tiles = angleData.map(angle => ({
70
+ angle,
71
+ actions: [],
72
+ decorations: []
73
+ }));
74
+ // Add actions
75
+ actions.forEach((action) => {
76
+ if (action.floor !== undefined && action.floor < this.tiles.length) {
77
+ this.tiles[action.floor].actions.push({ ...action });
78
+ }
79
+ });
80
+ // Add decorations
81
+ decorations.forEach((deco) => {
82
+ if ('floor' in deco) {
83
+ if (deco.floor >= this.tiles.length) {
84
+ this.tiles[this.tiles.length - 1].decorations.push({ ...deco });
85
+ }
86
+ else {
87
+ this.tiles[deco.floor].decorations.push({ ...deco });
88
+ }
89
+ }
90
+ });
91
+ }
92
+ getFileString() {
93
+ if (!this.filename)
94
+ return '';
95
+ return fs.readFileSync(this.filename, { encoding: this.encoding });
96
+ }
97
+ getFileDict() {
98
+ if (!this.filename) {
99
+ return {
100
+ angleData: [0],
101
+ settings: {},
102
+ actions: [],
103
+ decorations: []
104
+ };
105
+ }
106
+ const content = this.getFileString();
107
+ return JSON.parse(content);
108
+ }
109
+ getAngles() {
110
+ return this.tiles.map(tile => tile.angle);
111
+ }
112
+ setAngles(angles) {
113
+ if (angles.length > this.tiles.length) {
114
+ this.tiles.push({
115
+ angle: angles[angles.length - 1],
116
+ actions: [],
117
+ decorations: []
118
+ });
119
+ }
120
+ this.tiles = this.tiles.slice(0, angles.length);
121
+ this.tiles.forEach((tile, i) => {
122
+ tile.angle = angles[i];
123
+ });
124
+ }
125
+ getAnglesRelative(ignoreTwirls = false, padMidspins = false) {
126
+ const absangles = this.getAngles().slice(0, -1);
127
+ if (!ignoreTwirls) {
128
+ const twirls = this.getActions(action => action.eventType === 'Twirl')
129
+ .map(event => event.floor);
130
+ for (const twirl of twirls.reverse()) {
131
+ absangles.splice(twirl, absangles.length - twirl, ...absangles.slice(twirl).map(angle => angle !== 999 ? (2 * absangles[twirl - 1] - angle) % 360 : 999));
132
+ }
133
+ }
134
+ const midspins = absangles.map((angle, idx) => angle === 999 ? idx : -1).filter(idx => idx !== -1);
135
+ for (const midspin of midspins.reverse()) {
136
+ absangles.splice(midspin + 1, absangles.length - (midspin + 1), ...absangles.slice(midspin + 1).map(angle => angle !== 999 ? (angle + 180) % 360 : 999));
137
+ }
138
+ if (!padMidspins) {
139
+ absangles.splice(0, absangles.length, ...absangles.filter(angle => angle !== 999));
140
+ }
141
+ return absangles.map((angle, idx) => {
142
+ if (angle === 999)
143
+ return 0;
144
+ if (idx === 0)
145
+ return ((0 - angle + 180 - 1) % 360) + 1;
146
+ if (absangles[idx - 1] === 999) {
147
+ return ((absangles[idx - 2] - angle + 180 - 1) % 360) + 1;
148
+ }
149
+ return ((absangles[idx - 1] - angle + 180 - 1) % 360) + 1;
150
+ });
151
+ }
152
+ getActions(condition = () => true) {
153
+ return this.tiles.flatMap(tile => tile.actions.filter(condition));
154
+ }
155
+ getDecorations(condition = () => true) {
156
+ return [
157
+ ...this.tiles.flatMap(tile => tile.decorations.filter(condition)),
158
+ ...this.nonFloorDecos.filter(condition)
159
+ ];
160
+ }
161
+ writeToFile(filename) {
162
+ const final = {
163
+ angleData: this.tiles.map(tile => tile.angle),
164
+ settings: { ...this.settings },
165
+ actions: this.tiles.flatMap(tile => tile.actions),
166
+ decorations: [
167
+ ...this.tiles.flatMap(tile => tile.decorations),
168
+ ...this.nonFloorDecos
169
+ ]
170
+ };
171
+ const outputFile = filename || this.filename;
172
+ fs.writeFileSync(outputFile, JSON.stringify(final, null, 4), { encoding: this.encoding });
173
+ }
174
+ }
175
+ exports.Level = Level;
@@ -0,0 +1,17 @@
1
+ import { Action, Decoration } from './types';
2
+ export declare class LevelDict {
3
+ private filename;
4
+ private encoding;
5
+ private tiles;
6
+ private nonFloorDecos;
7
+ private settings;
8
+ constructor(filename?: string, encoding?: BufferEncoding);
9
+ private getFileString;
10
+ private getFileDict;
11
+ getAngles(): number[];
12
+ setAngles(angles: number[]): void;
13
+ getAnglesRelative(ignoreTwirls?: boolean, padMidspins?: boolean): number[];
14
+ getActions(condition?: (action: Action) => boolean): Action[];
15
+ getDecorations(condition?: (decoration: Decoration) => boolean): Decoration[];
16
+ writeToFile(filename?: string): void;
17
+ }
@@ -0,0 +1,135 @@
1
+ import * as fs from 'fs';
2
+ import * as utils from './utils/levelUtils';
3
+ import { InvalidLevelDictException } from './types';
4
+ export class LevelDict {
5
+ constructor(filename = '', encoding = 'utf-8') {
6
+ this.filename = filename;
7
+ this.encoding = encoding;
8
+ this.tiles = [];
9
+ this.nonFloorDecos = [];
10
+ this.settings = {};
11
+ const leveldict = this.getFileDict(filename);
12
+ if (!leveldict.actions || !leveldict.settings || (!leveldict.angleData && !leveldict.pathData)) {
13
+ throw new InvalidLevelDictException(`The provided .adofai file is invalid. (missing: ${!leveldict.actions ? 'actions' : ''} ${!leveldict.settings ? 'settings' : ''} ${!leveldict.angleData && !leveldict.pathData ? 'angleData or pathData' : ''})`);
14
+ }
15
+ let angleData;
16
+ if ('angleData' in leveldict) {
17
+ angleData = leveldict.angleData;
18
+ }
19
+ else {
20
+ const pathchars = {
21
+ 'R': 0, 'p': 15, 'J': 30, 'E': 45, 'T': 60, 'o': 75, 'U': 90,
22
+ 'q': 105, 'G': 120, 'Q': 135, 'H': 150, 'W': 165, 'L': 180,
23
+ 'x': 195, 'N': 210, 'Z': 225, 'F': 240, 'V': 255, 'D': 270,
24
+ 'Y': 285, 'B': 300, 'C': 315, 'M': 330, 'A': 345, '!': 999
25
+ };
26
+ angleData = Array.from(leveldict.pathData).map(char => pathchars[char]);
27
+ }
28
+ const actions = leveldict.actions;
29
+ const decorations = leveldict.decorations || [];
30
+ this.nonFloorDecos = decorations.filter((j) => !('floor' in j)).map((j) => ({ ...j }));
31
+ this.settings = filename !== '' ? { ...leveldict.settings } : {};
32
+ // Initialize tiles
33
+ this.tiles = angleData.map(angle => ({
34
+ angle,
35
+ actions: [],
36
+ decorations: []
37
+ }));
38
+ console.log('mapping tiles', angleData, this.tiles);
39
+ // Add actions
40
+ actions.forEach((action) => {
41
+ if (action.floor !== undefined && action.floor < this.tiles.length) {
42
+ this.tiles[action.floor].actions.push({ ...action });
43
+ }
44
+ });
45
+ // Add decorations
46
+ decorations.forEach((deco) => {
47
+ if ('floor' in deco) {
48
+ if (deco.floor >= this.tiles.length) {
49
+ this.tiles[this.tiles.length - 1].decorations.push({ ...deco });
50
+ }
51
+ else {
52
+ this.tiles[deco.floor].decorations.push({ ...deco });
53
+ }
54
+ }
55
+ });
56
+ }
57
+ getFileString(filename) {
58
+ if (!filename)
59
+ return '';
60
+ return fs.readFileSync(filename, { encoding: this.encoding });
61
+ }
62
+ getFileDict(filename) {
63
+ if (!filename) {
64
+ return {
65
+ angleData: [0],
66
+ settings: {},
67
+ actions: [],
68
+ decorations: []
69
+ };
70
+ }
71
+ const content = this.getFileString(filename);
72
+ return utils.safeParseJSON(content);
73
+ }
74
+ getAngles() {
75
+ return this.tiles.map(tile => tile.angle);
76
+ }
77
+ setAngles(angles) {
78
+ while (angles.length > this.tiles.length) {
79
+ this.tiles.push({
80
+ angle: angles[this.tiles.length],
81
+ actions: [],
82
+ decorations: []
83
+ });
84
+ }
85
+ this.tiles = this.tiles.slice(0, angles.length);
86
+ this.tiles.forEach((tile, i) => {
87
+ tile.angle = angles[i];
88
+ });
89
+ }
90
+ getAnglesRelative(ignoreTwirls = false, padMidspins = false) {
91
+ const absangles = this.getAngles();
92
+ if (!ignoreTwirls) {
93
+ const twirls = this.getActions(action => action.eventType === 'Twirl')
94
+ .map(event => event.floor);
95
+ for (const twirl of twirls.reverse()) {
96
+ absangles.splice(twirl, absangles.length - twirl, ...absangles.slice(twirl).map(angle => angle !== 999 ? (2 * absangles[twirl - 1] - angle) % 360 : 999));
97
+ }
98
+ }
99
+ const midspins = absangles.map((angle, idx) => angle === 999 ? idx : -1).filter(idx => idx !== -1);
100
+ for (const midspin of midspins.reverse()) {
101
+ absangles.splice(midspin + 1, absangles.length - (midspin + 1), ...absangles.slice(midspin + 1).map(angle => angle !== 999 ? (angle + 180) % 360 : 999));
102
+ }
103
+ if (!padMidspins) {
104
+ absangles.splice(0, absangles.length, ...absangles.filter(angle => angle !== 999));
105
+ }
106
+ return absangles.map((angle, idx) => {
107
+ if (angle === 999)
108
+ return 0;
109
+ if (idx === 0)
110
+ return ((0 - angle + 180 - 1) % 360) + 1;
111
+ if (absangles[idx - 1] === 999) {
112
+ return ((absangles[idx - 2] - angle + 180 - 1) % 360) + 1;
113
+ }
114
+ return ((absangles[idx - 1] - angle + 180 - 1) % 360) + 1;
115
+ });
116
+ }
117
+ getActions(condition = () => true) {
118
+ return this.tiles.flatMap(tile => tile.actions.filter(condition));
119
+ }
120
+ getDecorations(condition = () => true) {
121
+ return this.tiles.flatMap(tile => tile.decorations.filter(condition))
122
+ .concat(this.nonFloorDecos.filter(condition));
123
+ }
124
+ writeToFile(filename) {
125
+ const final = {
126
+ angleData: this.getAngles(),
127
+ settings: { ...this.settings },
128
+ actions: this.getActions(),
129
+ decorations: this.getDecorations()
130
+ };
131
+ console.log(final);
132
+ const outputFile = filename || this.filename;
133
+ fs.writeFileSync(outputFile, JSON.stringify(final, null, 4), { encoding: this.encoding });
134
+ }
135
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './LevelDict';
package/dist/index.js CHANGED
@@ -1,18 +1,2 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./types"), exports);
18
- __exportStar(require("./LevelDict"), exports);
1
+ export * from './types';
2
+ export * from './LevelDict';
@@ -0,0 +1,38 @@
1
+ export interface Action {
2
+ [key: string]: any;
3
+ floor?: number;
4
+ eventType?: string;
5
+ angleOffset?: number;
6
+ }
7
+ export interface Decoration {
8
+ [key: string]: any;
9
+ floor?: number;
10
+ }
11
+ export interface Settings {
12
+ requiredMods?: string[];
13
+ songFilename?: string;
14
+ artist?: string;
15
+ song?: string;
16
+ author?: string;
17
+ difficulty?: string;
18
+ bpm?: string;
19
+ backgroundColor?: string;
20
+ bgImage?: string;
21
+ showDefaultBGIfNoImage?: string;
22
+ zoom?: number;
23
+ [key: string]: any;
24
+ }
25
+ export interface Tile {
26
+ angle: number;
27
+ actions: Action[];
28
+ decorations: Decoration[];
29
+ }
30
+ export interface LevelJSON {
31
+ actions?: Array<Record<string, any>>;
32
+ decorations?: Array<Record<string, any>>;
33
+ settings?: Settings;
34
+ [key: string]: any;
35
+ }
36
+ export declare class InvalidLevelDictException extends Error {
37
+ constructor(message: string);
38
+ }
@@ -0,0 +1,6 @@
1
+ export class InvalidLevelDictException extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'InvalidLevelDictException';
5
+ }
6
+ }
@@ -0,0 +1,17 @@
1
+ import { Settings } from ".";
2
+ export interface LevelJSON {
3
+ actions?: Array<Record<string, any>>;
4
+ decorations?: Array<Record<string, any>>;
5
+ settings?: Settings;
6
+ [key: string]: any;
7
+ }
8
+ export interface TransformOptions {
9
+ keepEventTypes?: Set<string>;
10
+ dropEventTypes?: Set<string>;
11
+ baseCameraZoom?: number;
12
+ extraProtectedEventTypes?: Set<string>;
13
+ additionalPatterns?: Set<RegExp>;
14
+ constantBackgroundColor?: string;
15
+ removeForegroundFlash?: boolean;
16
+ dropFilters?: Set<string>;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { LevelJSON } from '../types/level';
2
+ /**
3
+ * Ensure bad comma handling
4
+ */
5
+ export declare function safeParseJSON(str: string): LevelJSON;
@@ -0,0 +1,16 @@
1
+ import JSON5 from 'json5';
2
+ /**
3
+ * Ensure bad comma handling
4
+ */
5
+ export function safeParseJSON(str) {
6
+ try {
7
+ const cleaned = str
8
+ .replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas before closing brackets
9
+ .replace(/([}\]])[\s\n]*([}\]])/g, '$1,$2') // Ensure comma between closing brackets
10
+ .replace(/([}\]])[\s\n]*("decorations"|"actions"|"settings")/g, '$1,$2'); // Ensure comma before main sections
11
+ return JSON5.parse(cleaned);
12
+ }
13
+ catch (e) {
14
+ throw new Error('Failed to parse level JSON');
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,9 +1,14 @@
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.2",
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
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
7
12
  "scripts": {
8
13
  "build": "tsc",
9
14
  "test": "jest",
@@ -15,7 +20,7 @@
15
20
  "adofai-lib-js",
16
21
  "adofai-lib-js-v0w4n"
17
22
  ],
18
- "author": "V0W4N (original by M1n3c4rt)",
23
+ "author": "V0W4N",
19
24
  "license": "MIT",
20
25
  "devDependencies": {
21
26
  "@types/jest": "^29.5.12",
@@ -23,5 +28,8 @@
23
28
  "jest": "^29.7.0",
24
29
  "ts-jest": "^29.1.2",
25
30
  "typescript": "^5.3.3"
31
+ },
32
+ "dependencies": {
33
+ "json5": "^2.2.3"
26
34
  }
27
35
  }
package/jest.config.js DELETED
@@ -1,6 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- testMatch: ['**/__tests__/**/*.test.ts'],
5
- moduleFileExtensions: ['ts', 'js'],
6
- };
package/src/LevelDict.ts DELETED
@@ -1,171 +0,0 @@
1
- import * as fs from 'fs';
2
- import { Action, Decoration, Settings, Tile, InvalidLevelDictException } from './types';
3
-
4
- export class LevelFile {
5
- private filename: string;
6
- private encoding: BufferEncoding;
7
- private tiles: Tile[];
8
- private nonFloorDecos: Decoration[];
9
- private settings: Settings;
10
-
11
- constructor(filename: string = '', encoding: BufferEncoding = 'utf-8') {
12
- this.filename = filename;
13
- this.encoding = encoding;
14
- this.tiles = [];
15
- this.nonFloorDecos = [];
16
- this.settings = {};
17
-
18
- const leveldict = this.getFileDict();
19
- if (!leveldict.actions || !leveldict.settings || (!leveldict.angleData && !leveldict.pathData)) {
20
- throw new InvalidLevelDictException(`The provided .adofai file is invalid. (missing: ${!leveldict.actions ? 'actions' : ''} ${!leveldict.settings ? 'settings' : ''} ${!leveldict.angleData && !leveldict.pathData ? 'angleData or pathData' : ''})`);
21
- }
22
-
23
- let angleData: number[];
24
- if ('angleData' in leveldict) {
25
- angleData = leveldict.angleData;
26
- } else {
27
- const pathchars: { [key: string]: number } = {
28
- 'R': 0, 'p': 15, 'J': 30, 'E': 45, 'T': 60, 'o': 75, 'U': 90,
29
- 'q': 105, 'G': 120, 'Q': 135, 'H': 150, 'W': 165, 'L': 180,
30
- 'x': 195, 'N': 210, 'Z': 225, 'F': 240, 'V': 255, 'D': 270,
31
- 'Y': 285, 'B': 300, 'C': 315, 'M': 330, 'A': 345, '!': 999
32
- };
33
- angleData = Array.from(leveldict.pathData as string).map(char => pathchars[char]);
34
- }
35
-
36
- angleData.push(angleData[angleData.length - 1] !== 999 ? angleData[angleData.length - 1] : (angleData[angleData.length - 2] + 180) % 360);
37
-
38
- const actions = leveldict.actions;
39
- const decorations = leveldict.decorations || [];
40
-
41
- this.nonFloorDecos = decorations.filter((j: Decoration) => !('floor' in j)).map((j: Decoration) => ({ ...j }));
42
- this.settings = filename !== '' ? { ...leveldict.settings } : {};
43
-
44
- // Initialize tiles
45
- this.tiles = angleData.map(angle => ({
46
- angle,
47
- actions: [],
48
- decorations: []
49
- }));
50
-
51
- // Add actions
52
- actions.forEach((action: Action) => {
53
- if (action.floor !== undefined && action.floor < this.tiles.length) {
54
- this.tiles[action.floor].actions.push({ ...action });
55
- }
56
- });
57
-
58
- // Add decorations
59
- decorations.forEach((deco: Decoration) => {
60
- if ('floor' in deco) {
61
- if (deco.floor! >= this.tiles.length) {
62
- this.tiles[this.tiles.length - 1].decorations.push({ ...deco });
63
- } else {
64
- this.tiles[deco.floor!].decorations.push({ ...deco });
65
- }
66
- }
67
- });
68
- }
69
-
70
- private getFileString(): string {
71
- if (!this.filename) return '';
72
- return fs.readFileSync(this.filename, { encoding: this.encoding });
73
- }
74
-
75
- private getFileDict(): any {
76
- if (!this.filename) {
77
- return {
78
- angleData: [0],
79
- settings: {},
80
- actions: [],
81
- decorations: []
82
- };
83
- }
84
-
85
- const content = this.getFileString();
86
- return JSON.parse(content);
87
- }
88
-
89
- public getAngles(): number[] {
90
- return this.tiles.map(tile => tile.angle);
91
- }
92
-
93
- public setAngles(angles: number[]): void {
94
- if (angles.length > this.tiles.length) {
95
- this.tiles.push({
96
- angle: angles[angles.length - 1],
97
- actions: [],
98
- decorations: []
99
- });
100
- }
101
- this.tiles = this.tiles.slice(0, angles.length);
102
- this.tiles.forEach((tile, i) => {
103
- tile.angle = angles[i];
104
- });
105
- }
106
-
107
- public getAnglesRelative(ignoreTwirls: boolean = false, padMidspins: boolean = false): number[] {
108
- const absangles = this.getAngles().slice(0, -1);
109
-
110
- if (!ignoreTwirls) {
111
- const twirls = this.getActions(action => action.eventType === 'Twirl')
112
- .map(event => event.floor!);
113
-
114
- for (const twirl of twirls.reverse()) {
115
- absangles.splice(twirl, absangles.length - twirl,
116
- ...absangles.slice(twirl).map(angle =>
117
- angle !== 999 ? (2 * absangles[twirl - 1] - angle) % 360 : 999
118
- )
119
- );
120
- }
121
- }
122
-
123
- const midspins = absangles.map((angle, idx) => angle === 999 ? idx : -1).filter(idx => idx !== -1);
124
- for (const midspin of midspins.reverse()) {
125
- absangles.splice(midspin + 1, absangles.length - (midspin + 1),
126
- ...absangles.slice(midspin + 1).map(angle =>
127
- angle !== 999 ? (angle + 180) % 360 : 999
128
- )
129
- );
130
- }
131
-
132
- if (!padMidspins) {
133
- absangles.splice(0, absangles.length, ...absangles.filter(angle => angle !== 999));
134
- }
135
-
136
- return absangles.map((angle, idx) => {
137
- if (angle === 999) return 0;
138
- if (idx === 0) return ((0 - angle + 180 - 1) % 360) + 1;
139
- if (absangles[idx - 1] === 999) {
140
- return ((absangles[idx - 2] - angle + 180 - 1) % 360) + 1;
141
- }
142
- return ((absangles[idx - 1] - angle + 180 - 1) % 360) + 1;
143
- });
144
- }
145
-
146
- public getActions(condition: (action: Action) => boolean = () => true): Action[] {
147
- return this.tiles.flatMap(tile => tile.actions.filter(condition));
148
- }
149
-
150
- 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
- ];
155
- }
156
-
157
- public writeToFile(filename?: string): void {
158
- const final = {
159
- angleData: this.tiles.map(tile => tile.angle),
160
- settings: { ...this.settings },
161
- actions: this.tiles.flatMap(tile => tile.actions),
162
- decorations: [
163
- ...this.tiles.flatMap(tile => tile.decorations),
164
- ...this.nonFloorDecos
165
- ]
166
- };
167
-
168
- const outputFile = filename || this.filename;
169
- fs.writeFileSync(outputFile, JSON.stringify(final, null, 4), { encoding: this.encoding });
170
- }
171
- }
@@ -1,83 +0,0 @@
1
- import { LevelFile } from '../LevelDict';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
-
5
- // Mock fs module
6
- jest.mock('fs');
7
-
8
- describe('LevelFile', () => {
9
- beforeEach(() => {
10
- jest.clearAllMocks();
11
- });
12
-
13
- it('should create an empty level file when no filename is provided', () => {
14
- const level = new LevelFile();
15
- expect(level.getAngles()).toEqual([0,0]);
16
- expect(level.getActions()).toEqual([]);
17
- expect(level.getDecorations()).toEqual([]);
18
- });
19
-
20
- it('should throw InvalidLevelDictException for invalid file', () => {
21
- (fs.readFileSync as jest.Mock).mockReturnValue('{}');
22
-
23
- expect(() => {
24
- new LevelFile('invalid.adofai');
25
- }).toThrow('The provided .adofai file is invalid');
26
- });
27
-
28
- it('should load a valid level file', () => {
29
- const mockLevelData = {
30
- angleData: [0, 90, 180],
31
- settings: { bpm: 120 },
32
- actions: [],
33
- decorations: []
34
- };
35
-
36
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockLevelData));
37
-
38
- const level = new LevelFile('test.adofai');
39
- expect(level.getAngles()).toEqual([0, 90, 180, 180]);
40
- });
41
-
42
- it('should write level file correctly', () => {
43
- const level = new LevelFile();
44
- level.setAngles([0, 90, 180]);
45
-
46
- level.writeToFile('output.adofai');
47
-
48
- expect(fs.writeFileSync).toHaveBeenCalledWith(
49
- 'output.adofai',
50
- expect.stringMatching(/"angleData"\s*:\s*\[\s*0\s*,\s*90\s*,\s*180\s*\]/),
51
- { encoding: 'utf-8' }
52
- );
53
- });
54
-
55
- it('should handle path-based level data', () => {
56
- const mockLevelData = {
57
- pathData: 'RUL',
58
- settings: { bpm: 120 },
59
- actions: [],
60
- decorations: []
61
- };
62
-
63
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockLevelData));
64
-
65
- const level = new LevelFile('test.adofai');
66
- expect(level.getAngles()).toEqual([0, 90, 180, 180]);
67
- });
68
-
69
- it('should get relative angles correctly', () => {
70
- const mockLevelData = {
71
- angleData: [0, 90, 180, 180],
72
- settings: { bpm: 120 },
73
- actions: [],
74
- decorations: []
75
- };
76
-
77
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockLevelData));
78
-
79
- const level = new LevelFile('test.adofai');
80
- const relativeAngles = level.getAnglesRelative();
81
- expect(relativeAngles).toEqual([180, 90, 90, 180]);
82
- });
83
- });
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './types';
2
- export * from './LevelDict';
@@ -1,28 +0,0 @@
1
- export interface Action {
2
- [key: string]: any;
3
- floor?: number;
4
- eventType?: string;
5
- angleOffset?: number;
6
- }
7
-
8
- export interface Decoration {
9
- [key: string]: any;
10
- floor?: number;
11
- }
12
-
13
- export interface Settings {
14
- [key: string]: any;
15
- }
16
-
17
- export interface Tile {
18
- angle: number;
19
- actions: Action[];
20
- decorations: Decoration[];
21
- }
22
-
23
- export class InvalidLevelDictException extends Error {
24
- constructor(message: string) {
25
- super(message);
26
- this.name = 'InvalidLevelDictException';
27
- }
28
- }
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2019",
4
- "module": "commonjs",
5
- "declaration": true,
6
- "outDir": "./dist",
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "moduleResolution": "node",
12
- "lib": ["es2019", "dom"]
13
- },
14
- "include": ["src/**/*"],
15
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
16
- }