# Guide

The following is a guide on how to use Beat Saber Deno, referred as `bsmap` for simpler term. If you
are familiar with basic JavaScript or new to TypeScript, this should be very familiar as it is about
what you expect it to do like a regular scripting. This does not cover everything but enough to get
good grasp at what it does.

## Examples

You may refer to example script if you need more glimpse on how to use them. Alternatively, check
out [my mapping scripts](https://github.com/KivalEvan/BeatSaber-MappingScript) to see how I would
use them.

## Importing

Create a `.ts` script file anywhere, be it desktop, map folder, or any dedicated place, and simply
add the following on top of the script. No additional file or setup needed, it just works.

```ts
// be sure to check for latest version on 'bsmap@version'
import * as bsmap from 'https://deno.land/x/bsmap@1.3.3/mod.ts';
```

**NOTE:** for first time user, you may need to cache the URL if the error pops up on import. Hover
over error and click on quick fix or `CTRL+.` on select. You may also need to initialise Deno
workspace if strange error regarding TS URL pops up.

For rolling release, visit [GitHub Repo](https://github.com/KivalEvan/BeatSaber-Deno) and import raw
file directly from there (`https://raw.githubusercontent.com/KivalEvan/BeatSaber-Deno/main/mod.ts`),
you may need to occasionally add `--reload` tag for latest update.

## Namespaces

Due to expansive library, namespace is used to separate functionality on their own area. Object
destructuring can be used to obtain certain variables and functions. Helpful tip, use `CTRL+Space`
to show list of available variables and functions.

```ts
import { load, save, utils, v3 } from 'https://deno.land/x/bsmap@1.3.3/mod.ts';
const { random, deepCopy } = utils;
```

List of available namespaces from root are `load`, `save`, `v2`, `v3`, `utils`, `globals`,
`convert`, `optimize`, `logger`, and `types`. Nested namespace is to be expected on an obscure area.

## Loading & Saving

To load & save the beatmap, a function is used to parse, validate, and optimise the respective info
and difficulty file.

```ts
const info = load.infoSync(); // not required

const data = load.difficultySync('HardStandard.dat'); // auto convert to v3 if it is not
const data2 = await load.difficulty('ExpertStandard.dat', 2, {
    directory: '/somewhere/else',
}); // advanced use, use or convert to v2
```

```ts
save.infoSync(info);

save.difficultySync(data);
await save.difficulty(data2, {
    directory: '/somewhere/else',
    filePath: 'overrideName.dat',
}); // advanced use
```

Difficulty file name is saved directly on difficulty class and can be changed, does not save the
file name inside custom data.

```ts
data.fileName = 'ExpertPlusStandard.dat';
```

If you happen to use the script outside of map folder, you can do the following before loading the
source folder and saving to target the folder. You may change this anytime whenever necessary.

```ts
globals.directory = './YOUR/MAP/FOLDER/PATH/';
```

**NOTE:** Windows typically uses `\` instead of `/` in path, this actually means escape character in
programming and would result in error. You may need to change the slash or escape character.

**NOTE:** Directory and file path will be overridden if explicitly provided in one of the following
load and save functions.

## Beatmap Object

All beatmap object is a class object as opposed to regular JSON object. This mean most array and
object will only accept class object. This enables extensive method and functionality to be used
directly from object. Custom data is always available and require no checking if exist.

### Creation

Each beatmap object including difficulty contain a static method `create` which allows you to
instantiate one or more objects. Partial or no data can be used to instantiate an object and will
use default value to fill the empty spot. This method always return object(s) in an array with an
exception being object that is not placed in array such as difficulty and index filter.

Object creation field can be mixed with either the schema field or the wrapped field, prioritises
wrapped field when presented.

```ts
const bomb = v3.BombNote.create();
const notes = v3.ColorNote.create(
    {},
    { b: 1, x: 0, y: 1 },
    {
        time: 2,
        posX: 1,
        posY: 0,
    },
    { b: 2, color: 1 },
);
data.colorNotes.push(...notes);
```

Difficulty class has a built-in method that allows instantiating of an object directly and insert
into an array. This also allows insertion of an already instantiated object.

```ts
data.addBasicEvents(
    { et: 3 },
    { time: 2, type: 1, value: 3 },
    {
        b: 5,
        type: 2,
        value: 7,
        f: 1,
    },
    {},
);
data.addBasicEvents(...events);
```

### Cloning

In modcharting, cloning is often used to create certain effect. This method can be used to clone an
existing object without referencing the original.

```ts
const original = v3.ColorNotes.create()[0];
const cloned = original.clone(); // new object with same property as original without reference
```

### Method Chaining

One liner or method chaining can be proven powerful in certain case scenarios.

```ts
const clones = notes.map((n) =>
    n
        .clone()
        .setTime(n.time + 4)
        .setDirection(8)
        .addCustomData({ color: [1, 1, 1] })
);
```

## Constants

The library provide constant variables in form of `PascalCase` or `SCREAMING_SNAKE_CASE` that can be
used to make your script slightly more readable but it is not necessarily needed.

```ts
const note = v3.ColorNotes.create({
    b: 24,
    c: NoteColor.RED,
    d: NoteDirection.ANY,
    x: PositionX.MIDDLE_LEFT,
    y: PositionY.BOTTOM,
})[0];

data.addBasicEvents({
    time: 10,
    type: EventType.BACK_LASERS,
    value: EventLightValue.WHITE_FADE,
});
```

## Extensions

This module is not available directly from main import as it is heavy, unstable, and make use of
third-party library. This provides plentiful of helpers that may be useful for modcharting and many
other purposes.

```ts
import * as chroma from 'https://deno.land/x/bsmap@1.3.3/extensions/chroma/mod.ts';
import * as NE from 'https://deno.land/x/bsmap@1.3.3/extensions/NE/mod.ts';
import * as selector from 'https://deno.land/x/bsmap@1.3.3/extensions/selector/mod.ts';
```

If you wish to import all of them, do as following:

```ts
import * as ext from 'https://deno.land/x/bsmap@1.3.3/extensions/mod.ts';
```

## Patch

This module is not included as it is very rarely used and unstable. It contains functions to attempt
fix and alter beatmap objects that were potentially broken or contain incompatible data.

```ts
import * as patch from 'https://deno.land/x/bsmap@1.3.3/patch/mod.ts';
```

## Addendum

### Dependency/Modular File

If you happen to work on multiple script files or has centralised folder for map scripting, a
dependency file can be used. Similarly, you can break your script into multiple file for modularity
purpose.

```ts
// deps.ts
export * from 'https://deno.land/x/bsmap@1.3.3/mod.ts';
export * as ext from 'https://deno.land/x/bsmap@1.3.3/extensions/mod.ts';
```

```ts
// map.ts
import * as bsmap from './deps.ts';
import { types, v3 } from './deps.ts';
```

### Typing

Static type is an incredibly powerful tool that can ensure type correctness of an object. This is
used extensively in the library and is encouraged to explore further into it by utilising type
casting. This is an intermediate knowledge of TypeScript but should be relatively easy to grasp.

```ts
const event = [{ c: 2 }, { b: 0.25, s: 0, i: 1 }] as Partial<types.v3.LightColorBase>[];
data.addLightColorEventBoxGroup({ e: [{ e: event }] });
```

### Logger

Contrary to popular belief, this is simply an output logging that can be controlled by level. This
can show and hide logging based on level.

```ts
bsmap.logger.setLevel(5); // completely hidden logging
bsmap.load.difficultySync('Test.dat');

bsmap.logger.setLevel(0); // verbose mode logging
bsmap.load.difficultySync('Test.dat');

bsmap.logger.setLevel(2); // default info logging
```

### Classic Scripting

If you prefer to script the old-fashioned way but would like to keep strong-typed schema, it is
possible but you may lose the ability to use certain utilities built around it.

```ts
const difficulty = load.difficultySync('ExpertPlusStandard.dat').toJSON();
const difficultyJSON = JSON.parse(
    Deno.readTextFileSync('ExpertPlusStandard.dat'),
) as types.v3.IDifficulty; // unsafe
```
