# Skyrim Platform

Skyrim Platform is a modding tool for Skyrim allowing writing scripts with JavaScript/TypeScript. One of the mods built on Skyrim Platform is skymp client. Yes, client of Skyrim Multiplayer is technically a mod for Skyrim Special Edition implemented using Skyrim Platform.

## Papyrus types from the original game

* All types in SkyrimPlatform have the same name as in Papyrus, for example: `Game`,`Actor`, `Form`,`Spell`, `Perk`, etc.
* To use types from Papyrus, including calling methods and static functions that they have, they need to be imported:

  ```typescript
  import { Game, Actor } from "../skyrimPlatform";
  ```

## Native functions

* Most types have a list of native functions, they are divided into static functions (`Native Global` in Papyrus) and methods (`Native`).
* Static functions are called on the type:

  ```typescript
  let sunX = Game.GetSunPositionX();
  let pl = Game.GetPlayer();
  Game.forceFirstPerson();
  ```
* Methods are called on the object:

  ```typescript
  let isPlayerInCombat = pl.isInCombat();
  ```
* A list of original game types with documentation can be found here: <https://www.creationkit.com/index.php?title=Category:Script_Objects>
* Calling functions from the original game is only available inside the `update` event handler (see below). If you try to do this in a different context, an exception will be thrown.

## Form

* Form (`Form`) is inherited by most game types, which have methods such as`Actor`, `Weapon`, etc.
* Each form has an ID, which is a 32-bit unsigned number (`uint32_t`). In SkyrimPlatform, represented by the type `number`.
* If you need to find a form by its ID, use `Game.getFormEx`. Note that it is `Game.getFormEx`, not`Game.getForm`. The latter always returns `null` for IDs above 0x80000000 (the behavior of the original game).
* You can get the form ID using the `getFormID` method. It is guaranteed that `Game.getFormEx` will find the form by the ID returned by this method if the form was not destroyed by the game.

## Safe use of objects

* After you get the object, you need to make sure that it is not `null`:

  ```typescript
  let actor = Game.findClosestActor(x, y, z, radius);
  if (actor) {
    let isInCombat = actor.isInCombat();
  }
  ```

  Or

  ```typescript
  let actor = Game.findClosestActor(x, y, z, radius);
  if (!actor) return;
  let isInCombat = actor.isInCombat();
  ```
* It is guaranteed that `Game.getPlayer` never returns`null`.

## Unhandled exceptions

* Unhandled JS exceptions will be logged to the console along with the call stack.
* Raw Promise rejections are also output to the console.
* Do not release plugins that have known bugs that are not handled. SkyrimPlatform performance is not guaranteed with unhandled exceptions.

## Object comparison

* To compare objects in SkyrimPlatform, you need to compare their IDs:

  ```typescript
  if (object1.getFormId() === object2.getFormId()) {
    // ...
  }
  ```

## Casting objects to string

* Types ported from Papyrus have limited support for a number of operations normal for regular JS objects such as `toString`,`toJSON`.

  ```typescript
  Game.getPlayer().ToString(); // '[object Actor]'
  JSON.stringify(Game.getPlayer()); // `{}`
  ```

## Cast

* If you have a `Form` object that is a weapon, and you need a`Weapon` object, you can use casting:

  ```typescript
  let sword = Game.getFormEx(swordId); // Get Form
  let weapon = Weapon.from(sword); // Cast to Weapon
  ```
* If you specify an ID for a form that is not actually a weapon, the `weapon` variable will be`null`.
* Passing `null` to the function for casting types as an argument will not throw an exception, but will return`null`:

  ```typescript
  ObjectReference.from(null); // null
  ```
* An attempt to cast to a type that has no instances or is incompatible in the inheritance hierarchy will also return `null`:

  ```typescript
  Game.from(Game.getPlayer()); // null
  Spell.from(Game.getPlayer()); // null
  ```
* You can also use typecasting to get an object of the base type, including `Form`:

  ```typescript
  let refr = ObjectReference.from(Game.getPlayer());
  let form = Form.from(refr);
  ```
* Casting an object to its own type will return the original object:

  ```typescript
  let actor = Actor.from(Game.getPlayer());
  ```

## Papyrus types added by SkyrimPlatform

* SkyrimPlatform currently only adds one type: `TESModPlatform`. Instances of this type do not exist by analogy with `Game`. Its static functions are listed below.
* `moveRefrToPosition` - teleports the object to the specified location and position.
* `setWeaponDrawnMode` - forces the actor to always keep the weapon drawn / removed.
* `getNthVtableElement` - gets the offset of the function from the virtual table (for reverse engineering).
* `getSkinColor` - gets the skin color of the ActorBase.
* `createNpc` - creates a new form of type ActorBase.
* `setNpcSex` - changes the gender of the ActorBase.
* `setNpcRace` - changes the race of the ActorBase.
* `setNpcSkinColor` - changes the skin color of the ActorBase.
* `setNpcHairColor` - changes the hair color of the ActorBase.
* `resizeHeadpartsArray` - resizes the array of head parts ActorBase.
* `resizeTintsArray` - resizes the main character's TintMasks array.
* `setFormIdUnsafe` - changes the form ID. Unsafe, use at your own risk.
* `clearTintMasks` - remove TintMasks for the given Actor or the Player Character if the Actor is not passed.
* `pushTintMask` - add TintMask with def. parameters for the given Actor or the Player Character, if Actor is not passed.
* `pushWornState`,`addItemEx` - add / remove items from def. ExtraData.
* `updateEquipment` - update equipment (unstable).
* `resetContainer` - clear the base container.

## Asynchronous

* Some game functions take time and happen in the background. Such functions in SkyrimPlatform return `Promise`:

  ```typescript
  Game.getPlayer()
    .SetPosition(0, 0, 0)
    .then(() => {
      printConsole("Teleported to the center of the world");
    });
  ```

  ```typescript
  Utility.wait(1).then(() => printConsole("1 second passed"));
  ```
* When called asynchronously, execution continues immediately:

  ```typescript
  Utility.wait(1);
  printConsole(`Will be displayed immediately, not after a second`);
  printConsole(`Should have used then`);
  ```
* You can use `async` /`await` to make the code look synchronous:

  ```typescript
  let f = async () => {
    await Utility.wait(1);
    printConsole("1 second passed");
  };
  ```

## Events

* At the moment, SkyrimPlatform has the ability to subscribe to your own events: `update` and`tick`.
* `update` is an event that is called once for every frame in the game (60 times per second at 60 FPS) after you've loaded a save or started a new game.

  ```typescript
  import { on } from "../skyrimPlatform";
  on("update", () => {
    // At this stage, the methods of all imported
    // types are already available.
  });
  ```
* `tick` is an event that is called once for every frame in the game immediately after the game starts.

  ```typescript
  import { on } from "../skyrimPlatform";
  on("tick", () => {
    // No access to game methods here.
  });
  ```
* And also for game events such as `effectStart`,`effectFinish`, `magicEffectApply`,`equip`, `unequip`,`hit`, `containerChanged`,`deathStart`, `deathEnd`,`loadGame`, `combatState`, `reset`,`scriptInit`, `trackedStats`,`uniqueIdChange`, `switchRaceComplete`,`cellFullyLoaded`, `grabRelease`,`lockChanged`, `moveAttachDetach`,`objectLoaded`, `waitStop`,`activate` ...
* With `on`, you can subscribe to the event forever.

  ```typescript
  import { on } from "../skyrimPlatform";
  on("equip", (event) => {
    printConsole(`actor: ${event.actor.getBaseObject().getName()}`);
    printConsole(`object: ${event.baseObj.getName()}`);
  });
  ```
* Using `once`, you can add a handler that will be called once the next time the event is fired.

  ```typescript
  import { once } from "../skyrimPlatform";
  once("equip", (event) => {
    printConsole(`actor: ${event.actor.getBaseObject().getName()}`);
    printConsole(`object: ${event.baseObj.getName()}`);
  });
  ```
* The variable `even` always contains variables related to the event to which you are subscribed.

## Hooks

* Hooks allow you to intercept the start and end of some functions of the game engine.
* Currently supported hooks: `sendAnimationEvent`

  ```typescript
  import { hooks, printConsole } from  "../skyrimPlatform"
  hooks.sendAnimationEvent.add({
      enter(ctx) {
          printConsole(ctx.animEventName);
      },
      leave(ctx) {
          if (ctx.animationSucceeded) printConsole(ctx.selfId);
      };
  });
  ```
* `enter` is called before starting the function. `ctx` contains the arguments passed to the function and also`storage` (see below).
* `leave` is called before the function ends. `ctx` contains the return value of the function, in addition to what was after the completion of`enter`.
* `ctx` is the same object for calls to`enter` and `leave`.
* `ctx.storage` is used to store data between calls to`enter` and `leave`.
* Script functions are not available inside the `enter` and`leave` handlers.

## Custom SkyrimPlatform Methods and Properties

* There are methods such as `printConsole ()` that can be called immediately after import. They do not belong to any of the game types.
* `printConsole (... arguments: any []): void` - output to the game console, opened by the `~` key.

  ```typescript
  import { printConsole, Game } from "../skyrimPlatform";
  on("update", () => {
    printConsole(`player id = ${Game.getPlayer().getFormID()}`);
  });
  ```
* `worldPointToScreenPoint` - convert an array of points in the game world to an array of points on the user's screen. The dot on the screen is indicated by 3 numbers from -1 to 1.
* `on (eventName: string, callback: any): void` - subscribe to an event named`eventName`.
* `callNative (className: string, functionName: string, self ?: object, ... args: any): any` - call a function from the original game by name.
* `getJsMemoryUsage (): number` - get the amount of RAM used by the embedded JS engine, in bytes.
* `storage` - an object used to save data between reloading scripts.
* `browser` is an object providing access to the Chromium Embedded Framework.
* `getExtraContainerChanges` - get ExtraContainerChanges of the given ObjectReference...
* `getContainer` - get all the items of the base container.
* `settings` - an object that provides access to plugin settings:

  ```typescript
  import { settings, printConsole } from "../skyrimPlatform";
  let option = settings["plugin-name"]["my-option"];
  printConsole(option);
  ```

  The plugin settings file is named `plugin-settings.txt` and should be located in the`Data / Platform / Plugins` folder. File format - JSON, extension `.txt` - for the convenience of users.

## Changing game console commands

* SkyrimPlatform allows you to change the implementation of any game console command, for such a modification you need to get the console command object by passing the command name to the `findConsoleCommand (commandName)` method, short or long.

  ```typescript
  let getAV = findConsoleCommand("GetActorValueInfo");
  let getAV = findConsoleCommand("GetAVInfo");
  ```
* Having received such an object, you can change the short (`shortName`) or long (`longName`) command name, as well as the number of accepted arguments (`numArgs`) and the function (`execute`) that will be executed when this console command is called via game console.

  ```typescript
  getAV.longName = "printArg";
  getAV.shortName = "";
  getAV.execute = (refrId: number, arg: string) => {
    printConsole(arg);
    return false;
  };
  ```
* The return value of your new implementation indicates whether the original function of this command will be executed.
* The first argument is the FormId of the object on which the console command is called, or 0 if it is absent.
* The rest of the parameters will be the arguments with which the console command was called, of type `string` or`number`.
* Since game functions are not available in this context, you must register an `update` event handler with`once` if you want to call a game function when you invoke a console command:

```typescript
getAV.longName = "ShowMessageBox";
getAV.shortName = "";

getAV.execute = (refrId: number, arg: string) => {
  once("update", () => {
    Debug.messageBox(arg);
  });
  return false;
};
```

## HTTP requests (experimental)

SkyrimPlatform provides limited support for HTTP requests. At the moment only `get` is available.

```typescript
import { HttpClient } from "../skyrimPlatfosrm";
let http = new HttpClient("vk.com", 80);
http.get("/").then((response) => printConsole(response.body));
```

* In case the request fails, `response.body` will be empty.

## Hot Reload

* Hot Reload for SkyrimPlatform plugins is supported. Changing the contents of `Data / Platform / Plugins` will reload all plugins without restarting the game.
* For full use, these are features, i.e. reload your plugin with Ctrl + S, take the example plugin as a basis <https://github.com/skyrim-multiplayer/skyrimplatform-plugin-example>
* When reloading plugins, the added event and hook handlers are removed, asynchronous operations are interrupted and all variables are reset, except for `storage` and its properties.

## DumpFunctions

* SkyrimPlatform has built-in functionality that allows you to output information about game functions to the file `Data / Platform / Output / DumpFunctions.txt` (key combination 9 + O + L). The game pauses for a few seconds while DumpFunctions is running.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://pospelovlm.gitbook.io/skyrim-multiplayer-docs/docs_skyrim_platform.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
