Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/**
* Interpolates between two objects, obj1 and obj2, based on a given factor
* If a property is a number and exists in both objects, it interpolates the values
* The result includes all properties from both objects
* Non-numeric properties and properties not shared between objects are copied as is
*
* @param {object} obj1 The first object
* @param {object} obj2 The second object
* @param {number} factor The interpolation factor (0 to 1).
* @returns {object} A new object with interpolated number values and other properties from both obj1 and obj2
*/
function interpolateObject(obj1, obj2, factor) {
const result = {};
for (const key in obj1) {
if (Object.hasOwn(obj1, key)) {
if (typeof obj1[key] === "number" && Object.hasOwn(obj2, key) && typeof obj2[key] === "number") {
result[key] = interpolate(obj1[key], obj2[key], factor);
} else if (typeof obj1[key] === "object" && Object.hasOwn(obj2, key) && typeof obj2[key] === "object") {
result[key] = interpolateObject(obj1[key], obj2[key], factor);
} else {
result[key] = obj1[key];
}
}
}
for (const key in obj2) {
if (Object.hasOwn(obj2, key) && !Object.hasOwn(result, key)) {
result[key] = obj2[key];
}
}
return result;
}
window.interpolateObject = interpolateObject;
/**
* Linearly interpolates between two values based on a given factor
*
* @param {number} value1
* @param {number} value2
* @param {number} factor Interpolation factor (0 to 1)
* @returns {number}
*/
function interpolate(value1, value2, factor) {
return value1 + (value2 - value1) * factor;
}
window.interpolate = interpolate;
function lerp(percent, start, end) {
return Math.clamp(start + (end - start) * percent, start, end);
}
window.lerp = lerp;
function inverseLerp(value, start, end) {
return Math.clamp((value - start) / (end - start), 0, 1);
}
window.inverseLerp = inverseLerp;
function formatDecimals(value, decimalPlaces) {
return Number(Math.round(parseFloat(value + "e" + decimalPlaces)) + "e-" + decimalPlaces);
}
window.formatDecimals = formatDecimals;
function nCr(n, r) {
// https://stackoverflow.com/questions/11809502/which-is-better-way-to-calculate-ncr
if (r > n - r) {
// because C(n, r) == C(n, n - r)
r = n - r;
}
let ans = 1;
for (let i = 1; i <= r; ++i) {
ans *= n - r + i;
ans /= i;
}
return ans;
}
window.nCr = nCr;
/**
* Checks if x is equal or higher than min and lower or equal to max
*
* @param {number} x
* @param {any} min
* @param {any} max
* @returns {boolean}
*/
function between(x, min, max) {
return typeof x === "number" && x >= min && x <= max;
}
window.between = between;
/**
* This function takes a value, and weights it by exponential curve.
*
* Value should be between 0.0 and 1.0 (use normalise to get a percentage of a max).
*
* An exponent of 1.0 returns 1 every time.
*
* Exponents between 1.0 and 2.0 return a curve favoring higher results (closer to 1)
*
* An exponent of 2.0 will return a flat line distribution, and is identical to random()
*
* Exponents greater than 2.0 return a curve favoring lower results (closer to 0), reaching to 0 at infinity.
*
* For example, see:
* https://www.desmos.com/calculator/87hhrjfixi
*
* @param {number} value Value to be weighted
* @param {number} exp Exponent used to generate the curve
* @returns {number} value weighted against exponential curve
*/
function expCurve(value, exp) {
return value ** exp / value;
}
window.expCurve = expCurve;
/**
* This function creates a random float 0.0-1.0, weighted by exponential curve.
*
* A value of 1.0 returns 1 every time.
*
* Values between 1.0 and 2.0 return a curve favoring higher results (closer to 1)
*
* A value of 2.0 will return a flat line distribution, and is identical to random()
*
* Values greater than 2.0 return a curve favoring lower results (closer to 0), reaching to 0 at infinity.
*
* For example, see:
* https://www.desmos.com/calculator/87hhrjfixi
*
* @param {number} exp Exponent used to generate the curve
* @returns {number} random number weighted against exponential curve
*/
function randomExp(exp) {
return expCurve(State.random(), exp);
}
window.randomExp = randomExp;
/**
* Normalises value to a decimal number 0.0-1.0, a percentage of the range specified in min and max.
*
* @param {number} value The value to be normalised
* @param {number} max The highest value of the range
* @param {number} min The lowest value of the range, default 0
* @returns {number} Normalised value
*/
function normalise(value, max, min = 0) {
const denominator = max - min;
if (denominator === 0) {
Errors.report("[normalise]: min and max params must be different.", { value, max, min });
return 0;
}
if (denominator < 0) {
Errors.report("[normalise]: max param must be greater than min param.", { value, max, min });
return 0;
}
return Math.clamp((value - min) / denominator, 0, 1);
}
window.normalise = normalise;
/**
* Returns a rounded number, with number of decimals based on the second parameter
*
* @param {number} number
* @param {number} decimals
* @returns {number} new number
*/
function round(number, decimals) {
const multiplier = 10 ** decimals;
return Math.round(number * multiplier) / multiplier;
}
window.round = round;
/**
* Categorises a value into a specified number of categories based on its position within a given range.
* The function automatically handles both ascending (min < max) and descending (min > max) ranges.
*
* @param {number} value The value to be categorized.
* @param {number} min The start of the range.
* @param {number} max The end of the range.
* @param {number} parts The number of categories into which the range should be divided.
* @returns {number} Returns the category index, ranging from 0 to parts-1.
* Examples:
* categorise(15, 10, 20, 4);
* Result: 2
* Divides the range 10-20 into 4 parts, and 15 falls into the third part. (First part is 0)
*
* categorise(15, 20, 10, 4);
* Result: 1
* Divides the range 20-10 into 4 parts, and 15 falls into the second part.
*
* categorise(5, 0, 10, 5);
* Result: 0
* Divides the range 0-10 into 5 parts, and 5 falls right on the border of the first and second part but is rounded down.
*
* categorise(18, 20, 10, 5);
* Result: 3
* Divides the range 20-10 into 5 parts, and 18 falls into the fourth part.
*/
function categorise(value, min, max, parts) {
const normalised = normalise(value, Math.max(min, max), Math.min(min, max));
const category = Math.floor(normalised * parts);
return Math.clamp(min > max ? parts - 1 - category : category, 0, parts - 1);
}
window.categorise = categorise;
/**
* Generates a random number within a specified range around a given base number.
*
* @param {number} num The base number.
* @param {number} min The minimum offset subtracted from the base number.
* @param {number} max The maximum offset added to the base number.
* @returns {number} A random number between `num - min` and `num + max`.
*/
const result = rngInstance ? rngInstance.randomFloat(num - min, num + max) : randomFloat(num - min, num + max);
return round(result, 2);
}
window.boundedRandom = boundedRandom;
/**
* Generates a random integer based on chance and max value
*
* @param {number} chance Value between 0 and 1
* @param {number} max Integer
*/
function calculateBinomial(chance, max) {
let result = 0;
for (let i = 0; i < max; i++) {