Mod Creation/Mod Context API Reference: Difference between revisions

Use SyntaxHighlight
(Use SyntaxHighlight)
Line 7: Line 7:
If the module is defined as the <code>"setup"</code> for your mod in <code>manifest.json</code>, the exported <code>setup</code> function will receive the context object as a sole parameter:
If the module is defined as the <code>"setup"</code> for your mod in <code>manifest.json</code>, the exported <code>setup</code> function will receive the context object as a sole parameter:


  <nowiki>export function setup(ctx) {
  <syntaxhighlight lang="js" line>export function setup(ctx) {
   // ...
   // ...
}</nowiki>
}</syntaxhighlight>


Otherwise, use the global <code>mod.getContext</code> method, passing in your module's meta object:
Otherwise, use the global <code>mod.getContext</code> method, passing in your module's meta object:


  <nowiki>const ctx = mod.getContext(import.meta);</nowiki>
  <syntaxhighlight lang="js" line>const ctx = mod.getContext(import.meta);</syntaxhighlight>


=== From a Script ===
=== From a Script ===
Line 19: Line 19:
The recommended approach for scripts is to use the global <code>mod.register</code> method. This only works in scripts injected via the <code>"load"</code> property of <code>manifest.json</code> or the <code>loadScript</code> method of the context object.
The recommended approach for scripts is to use the global <code>mod.register</code> method. This only works in scripts injected via the <code>"load"</code> property of <code>manifest.json</code> or the <code>loadScript</code> method of the context object.


  <nowiki>mod.register(ctx => {
  <syntaxhighlight lang="js" line>mod.register(ctx => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


=== From a Lifecycle Method ===
=== From a Lifecycle Method ===
Line 27: Line 27:
All game lifecycle method callbacks will also receive their respective mod's context object as a sole parameter.
All game lifecycle method callbacks will also receive their respective mod's context object as a sole parameter.


  <nowiki>onCharacterLoaded(ctx => {
  <syntaxhighlight lang="js" line>onCharacterLoaded(ctx => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


=== From the Dev Context ===
=== From the Dev Context ===
Line 35: Line 35:
For easier prototyping, you can use the global <code>mod.getDevContext</code> method to get a special dev mod context object. This should not be used in a mod, but only for development purposes (via the console). Any APIs that require resources will not work as the dev "mod" does not contain any resources.
For easier prototyping, you can use the global <code>mod.getDevContext</code> method to get a special dev mod context object. This should not be used in a mod, but only for development purposes (via the console). Any APIs that require resources will not work as the dev "mod" does not contain any resources.


  <nowiki>const devCtx = mod.getDevContext();</nowiki>
  <syntaxhighlight lang="js" line>const devCtx = mod.getDevContext();</syntaxhighlight>


== Loading Resources ==
== Loading Resources ==
Line 55: Line 55:
'''Example'''
'''Example'''


  <nowiki>const url = ctx.getResourceUrl('sea-shanty-2.ogg');
  <syntaxhighlight lang="js" line>const url = ctx.getResourceUrl('sea-shanty-2.ogg');
const song = new Audio(url);
const song = new Audio(url);
song.loop = true;
song.loop = true;
song.play();</nowiki>
song.play();</syntaxhighlight>


=== loadModule(path: string): Promise<any> ===
=== loadModule(path: string): Promise<any> ===
Line 74: Line 74:
'''Example'''
'''Example'''


  <nowiki>// my-module.mjs
  <syntaxhighlight lang="js" line>// my-module.mjs
export function greet(name) {
export function greet(name) {
   console.log(`Hello, ${name}!`);
   console.log(`Hello, ${name}!`);
}</nowiki>
}</syntaxhighlight>


  <nowiki>const myModule = await ctx.loadModule('my-module.mjs');
  <syntaxhighlight lang="js" line>const myModule = await ctx.loadModule('my-module.mjs');
myModule.greet('Melvor'); // Hello, Melvor!</nowiki>
myModule.greet('Melvor'); // Hello, Melvor!</syntaxhighlight>


=== loadScript(path: string): Promise<void> ===
=== loadScript(path: string): Promise<void> ===
Line 96: Line 96:
'''Example'''
'''Example'''


  <nowiki>// Await call if wanting the script to run before continuing
  <syntaxhighlight lang="js" line>// Await call if wanting the script to run before continuing
await ctx.loadScript('my-script.js');
await ctx.loadScript('my-script.js');
// my-script.js has run
// my-script.js has run
Line 102: Line 102:
// Don't await if no dependency on script
// Don't await if no dependency on script
ctx.loadScript('my-independednt-script.js');
ctx.loadScript('my-independednt-script.js');
// my-independent-script.js has NOT run yet</nowiki>
// my-independent-script.js has NOT run yet</syntaxhighlight>


=== loadTemplates(path: string): void ===
=== loadTemplates(path: string): void ===
Line 114: Line 114:
'''Example'''
'''Example'''


  <nowiki>ctx.loadTemplates('my-templates.html');</nowiki>
  <syntaxhighlight lang="js" line>ctx.loadTemplates('my-templates.html');</syntaxhighlight>


=== loadStylesheet(path: string): void ===
=== loadStylesheet(path: string): void ===
Line 126: Line 126:
'''Example'''
'''Example'''


  <nowiki>ctx.loadStylesheet('my-styles.css');</nowiki>
  <syntaxhighlight lang="js" line>ctx.loadStylesheet('my-styles.css');</syntaxhighlight>


=== loadData(path: string): Promise<any> ===
=== loadData(path: string): Promise<any> ===
Line 142: Line 142:
'''Example'''
'''Example'''


  <nowiki>// my-data.json
  <syntaxhighlight lang="js" line>// my-data.json
{
{
   "coolThings": [
   "coolThings": [
     "rocks"
     "rocks"
   ]
   ]
}</nowiki>
}</syntaxhighlight>


  <nowiki>// in JavaScript
  <syntaxhighlight lang="js" line>// in JavaScript
const myData = await ctx.loadData('my-data.json');
const myData = await ctx.loadData('my-data.json');
console.log(myData.coolThings); // ['rocks']</nowiki>
console.log(myData.coolThings); // ['rocks']</syntaxhighlight>


== Sharing Resources ==
== Sharing Resources ==
Line 165: Line 165:
'''Example'''
'''Example'''


  <nowiki>// manifest.json
  <syntaxhighlight lang="js" line>// manifest.json
{
{
   "namespace": "helloMelvor"
   "namespace": "helloMelvor"
}</nowiki>
}</syntaxhighlight>


  <nowiki>// in JavaScript
  <syntaxhighlight lang="js" line>// in JavaScript
ctx.share('my_cool_image.png');
ctx.share('my_cool_image.png');
ctx.share('Greeter.mjs');</nowiki>
ctx.share('Greeter.mjs');</syntaxhighlight>


Then another mod can use the resource anywhere that accepts a mod resource path.
Then another mod can use the resource anywhere that accepts a mod resource path.


  <nowiki>
  <syntaxhighlight lang="js" line>
ctx.getResourceUrl('helloMelvor:my_cool_image.png');
ctx.getResourceUrl('helloMelvor:my_cool_image.png');
const { Greeter } = await loadModule('helloMelvor:Greeter.mjs');
const { Greeter } = await loadModule('helloMelvor:Greeter.mjs');
const greeter = new Greeter();</nowiki>
const greeter = new Greeter();</syntaxhighlight>


== Lifecycle Hooks ==
== Lifecycle Hooks ==
Line 193: Line 193:
'''Example'''
'''Example'''


  <nowiki>ctx.onModsLoaded(async (ctx) => {
  <syntaxhighlight lang="js" line>ctx.onModsLoaded(async (ctx) => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


=== onCharacterSelectionLoaded(callback: (ctx: ModContext) => void | Promise<void>): void ===
=== onCharacterSelectionLoaded(callback: (ctx: ModContext) => void | Promise<void>): void ===
Line 207: Line 207:
'''Example'''
'''Example'''


  <nowiki>ctx.onCharacterSelectionLoaded(async (ctx) => {
  <syntaxhighlight lang="js" line>ctx.onCharacterSelectionLoaded(async (ctx) => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


=== onInterfaceAvailable(callback: (ctx: ModContext) => void | Promise<void>): void ===
=== onInterfaceAvailable(callback: (ctx: ModContext) => void | Promise<void>): void ===
Line 221: Line 221:
'''Example'''
'''Example'''


  <nowiki>ctx.onInterfaceAvailable(async (ctx) => {
  <syntaxhighlight lang="js" line>ctx.onInterfaceAvailable(async (ctx) => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


=== onCharacterLoaded(callback: (ctx: ModContext) => void | Promise<void>): void ===
=== onCharacterLoaded(callback: (ctx: ModContext) => void | Promise<void>): void ===
Line 235: Line 235:
'''Example'''
'''Example'''


  <nowiki>ctx.onCharacterLoaded(async (ctx) => {
  <syntaxhighlight lang="js" line>ctx.onCharacterLoaded(async (ctx) => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


=== onInterfaceReady(callback: (ctx: ModContext) => void | Promise<void>): void ===
=== onInterfaceReady(callback: (ctx: ModContext) => void | Promise<void>): void ===
Line 249: Line 249:
'''Example'''
'''Example'''


  <nowiki>ctx.onInterfaceReady(async (ctx) => {
  <syntaxhighlight lang="js" line>ctx.onInterfaceReady(async (ctx) => {
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


== Game Object Registration ==
== Game Object Registration ==
Line 267: Line 267:
'''Example'''
'''Example'''


  <nowiki>// data.json
  <syntaxhighlight lang="js" line>// data.json
{
{
   "$schema": "https://melvoridle.com/assets/schema/gameData.json",
   "$schema": "https://melvoridle.com/assets/schema/gameData.json",
Line 273: Line 273:
     // data objects here
     // data objects here
   }
   }
}</nowiki>
}</syntaxhighlight>


  <nowiki>await ctx.gameData.addPackage('data.json');</nowiki>
  <nowiki>await ctx.gameData.addPackage('data.json');</nowiki>
Line 291: Line 291:
'''Example'''
'''Example'''


  <nowiki>ctx.gameData.buildPackage((p) => {
  <syntaxhighlight lang="js" line>ctx.gameData.buildPackage((p) => {
   // data registration here
   // data registration here
});</nowiki>
});</syntaxhighlight>


==== BuiltGameDataPackage.package: GameDataPackage ====
==== BuiltGameDataPackage.package: GameDataPackage ====
Line 305: Line 305:
'''Example'''
'''Example'''


  <nowiki>const pkg = ctx.gameData.buildPackage((p) => { /* ... */ });
  <syntaxhighlight lang="js" line>const pkg = ctx.gameData.buildPackage((p) => { /* ... */ });
pkg.add();</nowiki>
pkg.add();</syntaxhighlight>


== Mod Settings ==
== Mod Settings ==
Line 326: Line 326:
'''Example'''
'''Example'''


  <nowiki>ctx.settings.section('General');
  <syntaxhighlight lang="js" line>ctx.settings.section('General');
ctx.settings.section('Other');
ctx.settings.section('Other');
// Sections will be displayed in the settings window in this order
// Sections will be displayed in the settings window in this order
// 1. General
// 1. General
// 2. Other</nowiki>
// 2. Other</syntaxhighlight>


==== Section.add(config: SettingConfig | SettingConfig[]): void ====
==== Section.add(config: SettingConfig | SettingConfig[]): void ====
Line 342: Line 342:
'''Example'''
'''Example'''


  <nowiki>ctx.settings.section('General').add({
  <syntaxhighlight lang="js" line>ctx.settings.section('General').add({
   type: 'switch',
   type: 'switch',
   name: 'awesomeness-detection',
   name: 'awesomeness-detection',
Line 358: Line 358:
     label: 'Pick a Number',
     label: 'Pick a Number',
     hint: '1 through 10'
     hint: '1 through 10'
}]);</nowiki>
}]);</syntaxhighlight>


==== Section.get(name: string): any ====
==== Section.get(name: string): any ====
Line 374: Line 374:
'''Example'''
'''Example'''


  <nowiki>// Assuming the player has typed "1" into the setting
  <syntaxhighlight lang="js" line>// Assuming the player has typed "1" into the setting
ctx.settings.section('Other').get('pick-a-number'); // 1</nowiki>
ctx.settings.section('Other').get('pick-a-number'); // 1</syntaxhighlight>


==== Section.set(name: string, value: any): void ====
==== Section.set(name: string, value: any): void ====
Line 389: Line 389:
'''Example'''
'''Example'''


  <nowiki>ctx.settings.section('Other').set('pick-a-number', 5);</nowiki>
  <syntaxhighlight lang="js" line>ctx.settings.section('Other').set('pick-a-number', 5);</syntaxhighlight>


=== type(name: string, config: SettingTypeConfig): void ===
=== type(name: string, config: SettingTypeConfig): void ===
Line 403: Line 403:
'''Example'''
'''Example'''


  <nowiki>// manifest.json
  <syntaxhighlight lang="js" line>// manifest.json
{
{
   "namespace": "my_mod",
   "namespace": "my_mod",
   // ...
   // ...
}</nowiki>
}</syntaxhighlight>


  <nowiki>ctx.settings.type('customText', {
  <syntaxhighlight lang="js" line>ctx.settings.type('customText', {
   // See example config in SettingTypeConfig section below
   // See example config in SettingTypeConfig section below
});
});


ctx.settings.section('Genera').add({
ctx.settings.section('General').add({
   type: 'customText',
   type: 'customText',
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


Other mods will have to add your namespace to use your custom type:
Other mods will have to add your namespace to use your custom type:


  <nowiki>ctx.settings.section('Other').add({
  <syntaxhighlight lang="js" line>ctx.settings.section('Other').add({
   type: 'my_mod:customText',
   type: 'my_mod:customText',
   // ...
   // ...
});</nowiki>
});</syntaxhighlight>


==== SettingTypeConfig ====
==== SettingTypeConfig ====
Line 441: Line 441:
Individual settings can opt to return validation errors in their <code>onChange</code> method. You can give a place to display this validation error in an element with a class of <code>validation-message</code>.
Individual settings can opt to return validation errors in their <code>onChange</code> method. You can give a place to display this validation error in an element with a class of <code>validation-message</code>.


  <nowiki>// The render function for a simple text box
  <syntaxhighlight lang="js" line>// The render function for a simple text box
function render(name, onChange, config) {
function render(name, onChange, config) {
   const input = document.createElement('input');
   const input = document.createElement('input');
Line 455: Line 455:
     const hint = document.createElement('small');
     const hint = document.createElement('small');
     hint.textContent = config.hint;
     hint.textContent = config.hint;
     label.appendChile(hint);
     label.appendChild(hint);
   }
   }


Line 467: Line 467:


   return root;
   return root;
}</nowiki>
}</syntaxhighlight>


===== get(root: HTMLElement): any =====
===== get(root: HTMLElement): any =====
Line 473: Line 473:
The <code>get</code> function is responsible for retrieving the current value of the setting. It receives just one parameter, the root HTML element returned from the render function, which can be useful for getting the current value.
The <code>get</code> function is responsible for retrieving the current value of the setting. It receives just one parameter, the root HTML element returned from the render function, which can be useful for getting the current value.


  <nowiki>// get function for simple text input defined by above render
  <syntaxhighlight lang="js" line>// get function for simple text input defined by above render
function get(root) {
function get(root) {
   return root.querySelector('input').value;
   return root.querySelector('input').value;
}</nowiki>
}</syntaxhighlight>


===== set(root: HTMLElement, value: any): void =====
===== set(root: HTMLElement, value: any): void =====
Line 482: Line 482:
The <code>set</code> function is responsible to keeping the HTML up-to-date with the current value (if updated programmatically). It receives the root HTML element from the render function and the value being set as the two parameters.
The <code>set</code> function is responsible to keeping the HTML up-to-date with the current value (if updated programmatically). It receives the root HTML element from the render function and the value being set as the two parameters.


  <nowiki>// set function for simple text setting defined above
  <syntaxhighlight lang="js" line>// set function for simple text setting defined above
function set(root, value) {
function set(root, value) {
   root.querySelector('input').value = value;
   root.querySelector('input').value = value;
}</nowiki>
}</syntaxhighlight>


==== Example ====
==== Example ====


  <nowiki>// Use functions defined in above examples as reference
  <syntaxhighlight lang="js" line>// Use functions defined in above examples as reference
ctx.settings.type('simpleText', {
ctx.settings.type('simpleText', {
   render: render,
   render: render,
   get: get,
   get: get,
   set: set
   set: set
});</nowiki>
});</syntaxhighlight>


=== Built-In Types ===
=== Built-In Types ===
Line 502: Line 502:
All individual settings inherit this base setting config object.
All individual settings inherit this base setting config object.


  <nowiki>interface SettingConfig {
  <syntaxhighlight lang="js" line>interface SettingConfig {
   type: string; // Type of the setting
   type: string; // Type of the setting
   name: string; // Name of the setting
   name: string; // Name of the setting
Line 509: Line 509:
   default: any; // Default value for the setting
   default: any; // Default value for the setting
   onChange(value: any, previousValue: any): void | boolean | string // See notes
   onChange(value: any, previousValue: any): void | boolean | string // See notes
}</nowiki>
}</syntaxhighlight>


The <code>onChange</code> option is a callback function that receives the new value being set and the previous value of the setting. This function can optionally return a value to serve as a validator:
The <code>onChange</code> option is a callback function that receives the new value being set and the previous value of the setting. This function can optionally return a value to serve as a validator:
Line 521: Line 521:
A simple textbox that accepts any character by default. Value is of type <code>string</code>.
A simple textbox that accepts any character by default. Value is of type <code>string</code>.


  <nowiki>interface TextConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface TextConfig implements SettingConfig {
   type: 'text';
   type: 'text';
   maxLength: number; // Max length attribute for the textbox
   maxLength: number; // Max length attribute for the textbox
}</nowiki>
}</syntaxhighlight>


==== Number ====
==== Number ====
Line 530: Line 530:
A simple textbox that only accepts numbers. Value is of type <code>number</code>.
A simple textbox that only accepts numbers. Value is of type <code>number</code>.


  <nowiki>interface NumberConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface NumberConfig implements SettingConfig {
   type: 'number';
   type: 'number';
   min: number; // Minimum value to be entered
   min: number; // Minimum value to be entered
   max: number; // Maximum value to be entered
   max: number; // Maximum value to be entered
}</nowiki>
}</syntaxhighlight>


==== Switch ====
==== Switch ====
Line 540: Line 540:
An on/off toggle switch. Value is of type <code>boolean</code>.
An on/off toggle switch. Value is of type <code>boolean</code>.


  <nowiki>interface SwitchConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface SwitchConfig implements SettingConfig {
   type: 'switch'
   type: 'switch'
}</nowiki>
}</syntaxhighlight>


==== Dropdown ====
==== Dropdown ====
Line 548: Line 548:
A dropdown button. Example: "Default Page on Load" game setting. Value is of type <code>any</code>.
A dropdown button. Example: "Default Page on Load" game setting. Value is of type <code>any</code>.


  <nowiki>DropdownConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>DropdownConfig implements SettingConfig {
   type: 'dropdown';
   type: 'dropdown';
   color: string; // see Button config
   color: string; // see Button config
   options: DropdownOption[]; // see note
   options: DropdownOption[]; // see note
}</nowiki>
}</syntaxhighlight>


The <code>options</code> option defines the dropdown options available to be selected. Dropdown option schema is:
The <code>options</code> option defines the dropdown options available to be selected. Dropdown option schema is:


  <nowiki>interface DropdownOption {
  <syntaxhighlight lang="js" line>interface DropdownOption {
   value: any; // value that is used by the setting
   value: any; // value that is used by the setting
   display: string | HTMLElement; // display text or element on the option
   display: string | HTMLElement; // display text or element on the option
}</nowiki>
}</syntaxhighlight>


==== Button ====
==== Button ====
Line 565: Line 565:
A button. Value is <code>undefined</code>.
A button. Value is <code>undefined</code>.


  <nowiki>interface ButtonConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface ButtonConfig implements SettingConfig {
   type: 'button';
   type: 'button';
   display: string | HTMLElement; // displayed text or element inside the button
   display: string | HTMLElement; // displayed text or element inside the button
   color: string; // see note
   color: string; // see note
   onClick(): void; // triggered on click of the button
   onClick(): void; // triggered on click of the button
}</nowiki>
}</syntaxhighlight>


The <code>color</code> option is appended to a CSS class starting with <code>btn-</code> and defaults to <code>primary</code> (<code>btn-primary</code>) if not defined. Default colors available:
The <code>color</code> option is appended to a CSS class starting with <code>btn-</code> and defaults to <code>primary</code> (<code>btn-primary</code>) if not defined. Default colors available:
Line 584: Line 584:
==== Checkbox Group ====
==== Checkbox Group ====
A group of checkboxes. Value is of type <code>any[]</code>.
A group of checkboxes. Value is of type <code>any[]</code>.
  <nowiki>interface CheckboxGroupConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface CheckboxGroupConfig implements SettingConfig {
   type: 'checkbox-group';
   type: 'checkbox-group';
   options: CheckboxOption[]; // see note
   options: CheckboxOption[]; // see note
}</nowiki>
}</syntaxhighlight>


The <code>options</code> option defines the checkboxes that are available to be selected. Checkbox option schema is:
The <code>options</code> option defines the checkboxes that are available to be selected. Checkbox option schema is:


<nowiki>interface CheckboxOption {
<syntaxhighlight lang="js" line>interface CheckboxOption {
   value: any; // value to be added to array that is set as setting value
   value: any; // value to be added to array that is set as setting value
   label: string | HTMLElement;
   label: string | HTMLElement;
   hint: string | HTMLElement;
   hint: string | HTMLElement;
}</nowiki>
}</syntaxhighlight>


==== Radio Group ====
==== Radio Group ====
A group of radio buttons. Value is of type <code>any</code>.
A group of radio buttons. Value is of type <code>any</code>.
  <nowiki>interface RadioGroupConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface RadioGroupConfig implements SettingConfig {
   type: 'radio-group';
   type: 'radio-group';
   options: CheckboxOption[]; // see checkbox group's options schema
   options: CheckboxOption[]; // see checkbox group's options schema
}</nowiki>
}</syntaxhighlight>


==== Label ====
==== Label ====
A simple label. Value is <code>undefined</code>.
A simple label. Value is <code>undefined</code>.
  <nowiki>interface LabelConfig implements SettingConfig {
  <syntaxhighlight lang="js" line>interface LabelConfig implements SettingConfig {
   type: 'label';
   type: 'label';
}</nowiki>
}</syntaxhighlight>


==== Custom ====
==== Custom ====
A custom-rendered setting. See [[#SettingTypeConfig|SettingTypeConfig]] section above. This is different from registering a custom setting type as this is a one-off and will not register the type for reuse. Value is of type <code>any</code>.
A custom-rendered setting. See [[#SettingTypeConfig|SettingTypeConfig]] section above. This is different from registering a custom setting type as this is a one-off and will not register the type for reuse. Value is of type <code>any</code>.
  <nowiki>interface CustomConfig implements SettingConfig, SettingTypeConfig {
  <syntaxhighlight lang="js" line>interface CustomConfig implements SettingConfig, SettingTypeConfig {
   type: 'custom';
   type: 'custom';
}</nowiki>
}</syntaxhighlight>


== Character Data Storage ==
== Character Data Storage ==
Line 638: Line 638:
'''Example'''
'''Example'''


  <nowiki>ctx.characterStorage.setItem('coolThings', ['rocks']);</nowiki>
  <syntaxhighlight lang="js" line>ctx.characterStorage.setItem('coolThings', ['rocks']);</syntaxhighlight>


=== getItem(key: string): any ===
=== getItem(key: string): any ===
Line 654: Line 654:
'''Example'''
'''Example'''


  <nowiki>ctx.characterStorage.getItem('coolThings'); // returns ['rocks']</nowiki>
  <syntaxhighlight lang="js" line>ctx.characterStorage.getItem('coolThings'); // returns ['rocks']</syntaxhighlight>


=== removeItem(key: string): void ===
=== removeItem(key: string): void ===
Line 666: Line 666:
'''Example'''
'''Example'''


  <nowiki>ctx.characterStorage.removeItem('coolThings');
  <syntaxhighlight lang="js" line>ctx.characterStorage.removeItem('coolThings');
ctx.characterStorage.getItem('coolThings'); // returns undefined</nowiki>
ctx.characterStorage.getItem('coolThings'); // returns undefined</syntaxhighlight>


=== clear(): void ===
=== clear(): void ===
Line 675: Line 675:
'''Example'''
'''Example'''


  <nowiki>ctx.characterStorage.clear();</nowiki>
  <syntaxhighlight lang="js" line>ctx.characterStorage.clear();</syntaxhighlight>


== Account Data Storage ==
== Account Data Storage ==
Line 699: Line 699:
'''Example'''
'''Example'''


  <nowiki>ctx.accountStorage.setItem('coolThings', ['rocks']);</nowiki>
  <syntaxhighlight lang="js" line>ctx.accountStorage.setItem('coolThings', ['rocks']);</syntaxhighlight>


=== getItem(key: string): any ===
=== getItem(key: string): any ===
Line 715: Line 715:
'''Example'''
'''Example'''


  <nowiki>ctx.accountStorage.getItem('coolThings'); // returns ['rocks']</nowiki>
  <syntaxhighlight lang="js" line>ctx.accountStorage.getItem('coolThings'); // returns ['rocks']</syntaxhighlight>


=== removeItem(key: string): void ===
=== removeItem(key: string): void ===
Line 727: Line 727:
'''Example'''
'''Example'''


  <nowiki>ctx.accountStorage.removeItem('coolThings');
  <syntaxhighlight lang="js" line>ctx.accountStorage.removeItem('coolThings');
ctx.accountStorage.getItem('coolThings'); // returns undefined</nowiki>
ctx.accountStorage.getItem('coolThings'); // returns undefined</syntaxhighlight>


=== clear(): void ===
=== clear(): void ===
Line 736: Line 736:
'''Example'''
'''Example'''


  <nowiki>ctx.accountStorage.clear();</nowiki>
  <syntaxhighlight lang="js" line>ctx.accountStorage.clear();</syntaxhighlight>


== Game Object Patching/Hooking ==
== Game Object Patching/Hooking ==
Line 756: Line 756:
'''Example'''
'''Example'''


  <nowiki>ctx.patch(Skill, 'addXP'); // Returns a MethodPatch
  <syntaxhighlight lang="js" line>ctx.patch(Skill, 'addXP'); // Returns a MethodPatch
ctx.patch(Skill, 'level'); // Returns a PropertyPatch</nowiki>
ctx.patch(Skill, 'level'); // Returns a PropertyPatch</syntaxhighlight>


==== MethodPatch.before(hook: (...args: any) => any[] | void): void ====
==== MethodPatch.before(hook: (...args: any) => any[] | void): void ====
Line 769: Line 769:
'''Example'''
'''Example'''


  <nowiki>// Double all XP gains
  <syntaxhighlight lang="js" line>// Double all XP gains
ctx.patch(Skill, 'addXP').before((amount, masteryAction) => [amount * 2, masteryAction]);</nowiki>
ctx.patch(Skill, 'addXP').before((amount, masteryAction) => [amount * 2, masteryAction]);</syntaxhighlight>


==== MethodPatch.after(hook: (returnValue: any, ...args: any) => any | void): void ====
==== MethodPatch.after(hook: (returnValue: any, ...args: any) => any | void): void ====
Line 782: Line 782:
'''Example'''
'''Example'''


  <nowiki>// The player never misses an attack
  <syntaxhighlight lang="js" line>// The player never misses an attack
ctx.patch(Player, 'rollToHit').after(willHit => {
ctx.patch(Player, 'rollToHit').after(willHit => {
   if (!willHit) console.log('A miss? I think not!');
   if (!willHit) console.log('A miss? I think not!');
   return true;
   return true;
});</nowiki>
});</syntaxhighlight>


==== MethodPatch.replace(replacement: (replacedMethod: (...args: any) => any, ...args: any) => any): void ====
==== MethodPatch.replace(replacement: (replacedMethod: (...args: any) => any, ...args: any) => any): void ====
Line 798: Line 798:
'''Example'''
'''Example'''


  <nowiki>ctx.patch(Skill, 'addXP').replace(function(o, amount, masteryAction) {
  <syntaxhighlight lang="js" line>ctx.patch(Skill, 'addXP').replace(function(o, amount, masteryAction) {
   // Prevent any woodcutting XP
   // Prevent any woodcutting XP
   if (this.id === 'melvorD:Woodcutting') return;
   if (this.id === 'melvorD:Woodcutting') return;
Line 807: Line 807:
   // Grant all other XP as normal
   // Grant all other XP as normal
   return o(amount, masteryAction);
   return o(amount, masteryAction);
});</nowiki>
});</syntaxhighlight>


It's important to note that using the <code>replace</code> method replaces the current method body, meaning multiple calls of the <code>replace</code> method get executed in the reverse order that they were declared:
It's important to note that using the <code>replace</code> method replaces the current method body, meaning multiple calls of the <code>replace</code> method get executed in the reverse order that they were declared:


  <nowiki>const xpPatch = ctx.patch(Skill, 'addXP');
  <syntaxhighlight lang="js" line>const xpPatch = ctx.patch(Skill, 'addXP');


xpPatch.replace((o, amount, masteryAction) => {
xpPatch.replace((o, amount, masteryAction) => {
Line 826: Line 826:
// Logs:
// Logs:
// Replacement #2
// Replacement #2
// Replacement #1</nowiki>
// Replacement #1</syntaxhighlight>


==== PropertyPatch.get(getter: (o: () => any) => any): void ====
==== PropertyPatch.get(getter: (o: () => any) => any): void ====
Line 838: Line 838:
'''Example'''
'''Example'''


  <nowiki>// Effectively double available Township resources
  <syntaxhighlight lang="js" line>// Effectively double available Township resources
ctx.patch(TownshipResource, 'amount').get((o) => o() * 2);
ctx.patch(TownshipResource, 'amount').get((o) => o() * 2);
// Or more practically, make resources unlimited
// Or more practically, make resources unlimited
ctx.patch(TownshipResource, 'amount').get(() => 999999);</nowiki>
ctx.patch(TownshipResource, 'amount').get(() => 999999);</syntaxhighlight>


==== PropertyPatch.set(setter: (o: (value: any) => void, value: any) => void): void ====
==== PropertyPatch.set(setter: (o: (value: any) => void, value: any) => void): void ====
Line 853: Line 853:
'''Example'''
'''Example'''


  <nowiki>// Sorry, there aren't many setters in the game to use for a practical example
  <syntaxhighlight lang="js" line>// Sorry, there aren't many setters in the game to use for a practical example
// Doubles whatever resource amount is being set
// Doubles whatever resource amount is being set
ctx.patch(TownshipResource, 'amount').set((o, amount) => o(amount * 2));
ctx.patch(TownshipResource, 'amount').set((o, amount) => o(amount * 2));
Line 859: Line 859:
game.township.resources.getObjectByID('melvorF:Wood').amount = 1000;
game.township.resources.getObjectByID('melvorF:Wood').amount = 1000;
game.township.renderQueue.resourceAmounts = true;
game.township.renderQueue.resourceAmounts = true;
// 2000 wood is available</nowiki>
// 2000 wood is available</syntaxhighlight>


==== PropertyPatch.replace(getter?: (o: () => any) => any, setter?: (o: (value: any) => void, value: any) => void): void ====
==== PropertyPatch.replace(getter?: (o: () => any) => any, setter?: (o: (value: any) => void, value: any) => void): void ====
Line 891: Line 891:
'''Example'''
'''Example'''


  <nowiki>ctx.isPatched(Skill, 'addXP'); // false
  <syntaxhighlight lang="js" line>ctx.isPatched(Skill, 'addXP'); // false
ctx.patch(Skill, 'addXP');
ctx.patch(Skill, 'addXP');
ctx.isPatched(Skill, 'addXP'); // true</nowiki>
ctx.isPatched(Skill, 'addXP'); // true</syntaxhighlight>


== Exposing Properties and Methods (Mod API) ==
== Exposing Properties and Methods (Mod API) ==
Line 913: Line 913:
'''Example'''
'''Example'''


  <nowiki>// manifest.json
  <syntaxhighlight lang="js" line>// manifest.json
{
{
   "namespace": "helloWorld",
   "namespace": "helloWorld",
   "setup": "setup.mjs"
   "setup": "setup.mjs"
}</nowiki>
}</syntaxhighlight>


  <nowiki>// setup.mjs
  <syntaxhighlight lang="js" line>// setup.mjs
export function setup({ api }) {
export function setup({ api }) {
   api({
   api({
     greet: name => console.log(`Hello, ${name!}`);
     greet: name => console.log(`Hello, ${name!}`);
   });
   });
}</nowiki>
}</syntaxhighlight>


Other mods would then be able to interact with your API:  
Other mods would then be able to interact with your API:  


  <nowiki>// some other mod
  <syntaxhighlight lang="js" line>// some other mod
mod.api.helloWorld.greet('Melvor'); // Hello, Melvor!</nowiki>
mod.api.helloWorld.greet('Melvor'); // Hello, Melvor!</syntaxhighlight>
{{ModGuideNav}}
{{ModGuideNav}}
{{Menu}}