Utilities
Estimated reading time: 7 minutes
Utilities are shared APIs for mod scripts. They reduce repeated code around:
- Cloning, comparing, and merging objects or arrays.
- Checking containment in arrays, objects,
Set, Map, and strings.
- Picking random array items, including weighted choices.
- Converting string case.
- Clamping numbers.
- Checking image resources.
- Handling text, JSON, bytes, Base64, and paths.
The framework installs prototype/static helpers during initialization, and also exposes common helpers globally.
Preferred Style
Use prototype methods when operating on an existing value:
const copy = source.clone();
const same = oldData.equal(newData);
const ok = tags.contains('beast');
const key = 'My Text'.convert('snake');
Use static methods when creating a new object or array:
const options = Object.merge(defaultOptions, userOptions);
const list = Array.append(baseList, modList);
Number helpers live on Math:
const value = Math.clamp(input, 0, 100);
Prototype vs Static Methods
Prototype merge methods mutate the receiver:
target.merge(source);
target.append(source);
target.cover(source);
Static methods create a new object or array:
const next = Object.merge(defaults, current);
const list = Array.append(base, extra);
Prefer Object.merge(), Object.append(), Object.cover(), Array.merge(), Array.append(), and Array.cover() when you do not want to mutate existing data.
Common Methods
clone
const deepCopy = source.clone();
const shallowCopy = source.clone(false);
const plainCopy = source.clone(true, false);
Arguments:
Supports plain objects, arrays, Date, RegExp, Map, Set, ArrayBuffer, DataView, and TypedArray values.
clone() handles circular references. Non-enumerable properties are not copied.
equal
const same = dataA.equal(dataB);
equal() performs deep comparison, which is useful for objects, arrays, and nested structures.
merge / append / cover
These methods recursively merge objects. Their main difference is array handling.
({ list: [1, 2] }).merge({ list: [3] }); // { list: [3, 2] }
({ list: [1, 2] }).append({ list: [3] }); // { list: [1, 2, 3] }
({ list: [1, 2] }).cover({ list: [3] }); // { list: [3] }
Objects are merged recursively:
const result = Object.merge(
{ npc: { enabled: true, count: 2 } },
{ npc: { count: 4 } }
);
// { npc: { enabled: true, count: 4 } }
Multiple sources are applied in order; later sources win:
const options = Object.merge(defaults, modDefaults, playerOptions);
mergefn / appendfn / coverfn
Filtered variants call a filter before each field is merged.
target.mergefn((key, value, depth, targetValue) => targetValue === undefined, source);
Filter arguments:
Examples:
target.mergefn((_key, _value, _depth, targetValue) => targetValue === undefined, source);
target.mergefn((_key, _value, depth) => depth <= 2, source);
target.mergefn((_key, value) => value != null, source);
contains
Arrays:
[1, 2, 3].contains(2); // true
[1, 2, 3].contains([1, 2], 'all'); // true
[1, 2, 3].contains([2, 4], 'any'); // true
[1, 2, 3].contains([4, 5], 'none'); // true
Objects, Set, and Map check their values:
({ a: 1, b: 2 }).contains(2); // true
new Set(['a', 'b']).contains('a'); // true
new Map([['key', 'value']]).contains('value'); // true
Strings:
'Hello World'.contains('World'); // true
'Hello World'.contains('hello', { case: false }); // true
Modes:
Options:
const list = [{ id: 1 }, { id: 2 }];
list.contains({ id: 1 }, 'any', { deep: true }); // true
list.contains(2, 'any', { compare: (item, value) => item.id === value }); // true
random / either
Math.random() keeps native behavior when called without arguments:
Math.random(); // Float from 0 to 1
With arguments, framework overloads apply:
Math.random(10); // Integer from 0 to 10
Math.random(5, 10); // Integer from 5 to 10
Math.random(5, 10, true); // Float from 5 to 10
Array random:
['a', 'b', 'c'].random();
Weighted choice:
['rare', 'normal'].either([0.1, 0.9]);
Allow null:
['a', 'b'].either(undefined, true);
For reproducible random sequences, use randSystem.
convert
'Hello World'.convert('snake'); // hello_world
'Hello World'.convert('kebab'); // hello-world
'hello world'.convert('pascal'); // HelloWorld
'hello world'.convert('camel'); // helloWorld
Supported modes:
Options:
'NPC name'.convert('title'); // NPC Name
'NPC name'.convert('title', { acronym: false }); // Npc Name
Math.clamp
Math.clamp('12.5', 0, 100); // 12.5
Math.clamp(120, 0, 100); // 100
Math.clamp(undefined, 0, 100, 10); // 10
fallback is used only when the input cannot become a finite number. If omitted, the lower bound is used.
min and max may be passed in reverse order:
Math.clamp(5, 10, 0); // 5
loadImage
const result = await loadImage('img/myMod/icon.png');
if (result) {
console.log('Image is available');
}
loadImage() first asks ModLoader for the image. If that fails, it checks the path directly.
Possible return values:
Use await in async flows.
Bytes and Base64
These helpers are useful for cloud saves, import/export, compression, and network requests.
const bytes = textToBytes('hello');
const text = bytesToText(bytes);
const jsonBytes = jsonToBytes({ ok: true });
const data = bytesToJson(jsonBytes);
const base64 = bytesToBase64(bytes);
const bytesAgain = base64ToBytes(base64);
const buffer = base64ToArrayBuffer(base64);
Path and Text Helpers
trimSlashes('/a/b/'); // a/b
joinPath('/cloud/', '/slot/', '1'); // cloud/slot/1
joinEncodedPath('user name', 'slot 1'); // user%20name/slot%201
escapeHtmlText('<b>text</b>'); // <b>text</b>
widgets() cleans a .twee import by removing the outer passage declaration.
import Options from '@/twee/Options.twee';
const content = widgets(Options);
Passing multiple contents returns an array:
const list = widgets(Options, Cheats);
SelectCase
SelectCase is useful for writing chained condition/result tables.
const result = new SelectCase()
.case('wolf', 'Wolf')
.caseIn(['cat', 'dog'], 'Animal')
.caseRange(0, 10, 'Low')
.caseIncludes('NPC', 'Character')
.caseRegex(/^mod:/, 'Mod')
.else('Unknown')
.match(value);
Common methods:
Global Functions
Common helpers are also available globally:
clone(source);
equal(a, b);
merge(target, source);
append(target, source);
cover(target, source);
contains(list, value);
random(1, 10);
either(list);
convert('Hello World', 'snake');
clamp(value, 0, 100);
loadImage(src);
New code should prefer prototype/static style because it makes the operated value clearer.