mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-22 10:05:37 -04:00
3165408b17
Only show in developer changelog
815 lines
25 KiB
TypeScript
815 lines
25 KiB
TypeScript
/*
|
|
* GDevelop JS Platform
|
|
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
|
* This project is released under the MIT License.
|
|
*/
|
|
|
|
/**
|
|
* The `gdjs` namespace contains all classes and objects of the game engine.
|
|
* @namespace gdjs
|
|
*/
|
|
namespace gdjs {
|
|
const logger = new gdjs.Logger('Engine runtime');
|
|
const hexStringRegex = /^(#{0,1}[A-Fa-f0-9]{6})$/;
|
|
const shorthandHexStringRegex = /^(#{0,1}[A-Fa-f0-9]{3})$/;
|
|
|
|
/**
|
|
* Contains functions used by events (this is a convention only, functions can actually
|
|
* be anywhere).
|
|
* @memberOf gdjs
|
|
* @category Core Engine > Events interfacing
|
|
*/
|
|
export namespace evtTools {
|
|
// @ts-ignore - This variable is unused on purpose.
|
|
const thisIsUnusedButEnsureTheNamespaceIsDeclared = true;
|
|
}
|
|
|
|
/** @internal */
|
|
export const objectsTypes = new Hashtable<typeof gdjs.RuntimeObject>();
|
|
|
|
/** @internal */
|
|
export const behaviorsTypes = new Hashtable<typeof gdjs.RuntimeBehavior>();
|
|
|
|
type RuntimeSceneCallback = (runtimeScene: gdjs.RuntimeScene) => void;
|
|
type InGameEditorCallback = (editor: gdjs.InGameEditor) => void;
|
|
type RuntimeSceneRuntimeObjectCallback = (
|
|
instanceContainer: gdjs.RuntimeInstanceContainer,
|
|
runtimeObject: gdjs.RuntimeObject
|
|
) => void;
|
|
type RuntimeSceneGetSyncDataCallback = (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
currentSyncData: LayoutNetworkSyncData,
|
|
syncOptions: GetNetworkSyncDataOptions
|
|
) => void;
|
|
type RuntimeSceneUpdateFromSyncData = (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
receivedSyncData: LayoutNetworkSyncData,
|
|
options: UpdateFromNetworkSyncDataOptions
|
|
) => void;
|
|
|
|
/** @internal */
|
|
export const callbacksFirstRuntimeSceneLoaded: Array<RuntimeSceneCallback> =
|
|
[];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeSceneLoaded: Array<RuntimeSceneCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeScenePreEvents: Array<RuntimeSceneCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeScenePostEvents: Array<RuntimeSceneCallback> =
|
|
[];
|
|
|
|
/** @internal */
|
|
export const callbacksInGameEditorPostStep: Array<InGameEditorCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeScenePaused: Array<RuntimeSceneCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeSceneResumed: Array<RuntimeSceneCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeSceneUnloading: Array<RuntimeSceneCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeSceneUnloaded: Array<RuntimeSceneCallback> = [];
|
|
|
|
/** @internal */
|
|
export const callbacksObjectDeletedFromScene: Array<RuntimeSceneRuntimeObjectCallback> =
|
|
[];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeSceneGetSyncData: Array<RuntimeSceneGetSyncDataCallback> =
|
|
[];
|
|
|
|
/** @internal */
|
|
export const callbacksRuntimeSceneUpdateFromSyncData: Array<RuntimeSceneUpdateFromSyncData> =
|
|
[];
|
|
|
|
/**
|
|
* Base64 encoded logo of GDevelop for the splash screen.
|
|
* @internal
|
|
*/
|
|
export let gdevelopLogo: string = '';
|
|
|
|
/**
|
|
* Convert a RGB object to a Hex string.
|
|
*
|
|
* No "#" or "0x" are added.
|
|
* @param r Red
|
|
* @param g Green
|
|
* @param b Blue
|
|
* @category Utils > Color
|
|
*/
|
|
export const rgbToHex = function (
|
|
r: integer,
|
|
g: integer,
|
|
b: integer
|
|
): string {
|
|
return '' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
|
};
|
|
|
|
/**
|
|
* Convert a Hex string (#124FE4) to an RGB color array [r, g, b], where each component is in the range [0, 255].
|
|
*
|
|
* @param {string} hex Color hexadecimal
|
|
* @category Utils > Color
|
|
*/
|
|
export const hexToRGBColor = function (
|
|
hexString: string
|
|
): [number, number, number] {
|
|
const hexNumber = parseInt(hexString.replace('#', ''), 16);
|
|
return Number.isFinite(hexNumber)
|
|
? [(hexNumber >> 16) & 0xff, (hexNumber >> 8) & 0xff, hexNumber & 0xff]
|
|
: [0, 0, 0];
|
|
};
|
|
|
|
/**
|
|
* Convert a shorthand Hex string (#1F4) to an RGB color array [r, g, b], where each component is in the range [0, 255].
|
|
*
|
|
* @param {string} hex Color hexadecimal
|
|
* @category Utils > Color
|
|
*/
|
|
export const shorthandHexToRGBColor = function (
|
|
hexString: string
|
|
): [number, number, number] {
|
|
const hexNumber = parseInt(hexString.replace('#', ''), 16);
|
|
return Number.isFinite(hexNumber)
|
|
? [
|
|
17 * ((hexNumber >> 8) & 0xf),
|
|
17 * ((hexNumber >> 4) & 0xf),
|
|
17 * (hexNumber & 0xf),
|
|
]
|
|
: [0, 0, 0];
|
|
};
|
|
|
|
/**
|
|
* Convert a RGB string ("rrr;ggg;bbb") or a Hex string ("#rrggbb") to a RGB color array ([r,g,b] with each component going from 0 to 255).
|
|
* @param value The color as a RGB string or Hex string
|
|
* @category Utils > Color
|
|
*/
|
|
export const rgbOrHexToRGBColor = function (
|
|
value: string
|
|
): [number, number, number] {
|
|
// TODO Add a `result` parameter to allow to reuse the returned array.
|
|
if (!value.startsWith('#')) {
|
|
const splitValue = value.split(';');
|
|
// If a RGB string is provided, return the RGB object.
|
|
if (splitValue.length === 3) {
|
|
return [
|
|
Math.min(255, parseInt(splitValue[0], 10)),
|
|
Math.min(255, parseInt(splitValue[1], 10)),
|
|
Math.min(255, parseInt(splitValue[2], 10)),
|
|
];
|
|
}
|
|
}
|
|
|
|
const hexColor = extractHexString(value);
|
|
if (hexColor) {
|
|
return hexToRGBColor(hexColor);
|
|
}
|
|
const shorthandHexColor = extractShorthandHexString(value);
|
|
if (shorthandHexColor) {
|
|
return shorthandHexToRGBColor(shorthandHexColor);
|
|
}
|
|
return [0, 0, 0];
|
|
};
|
|
|
|
/**
|
|
* Convert a RGB string ("rrr;ggg;bbb") or a Hex string ("#rrggbb") to a RGB color number.
|
|
* @param rgbOrHexString The color as a RGB string or Hex string
|
|
* @category Utils > Color
|
|
*/
|
|
export const rgbOrHexStringToNumber = (rgbOrHexString: string): integer => {
|
|
const components = gdjs.rgbOrHexToRGBColor(rgbOrHexString);
|
|
return gdjs.rgbToHexNumber(components[0], components[1], components[2]);
|
|
};
|
|
|
|
/**
|
|
* Convert a RGB object to a Hex number.
|
|
* @param r Red
|
|
* @param g Green
|
|
* @param b Blue
|
|
* @category Utils > Color
|
|
*/
|
|
export const rgbToHexNumber = function (
|
|
r: float,
|
|
g: float,
|
|
b: float
|
|
): integer {
|
|
return (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b);
|
|
};
|
|
|
|
/**
|
|
* Convert a Hex number to a RGB color object ({r,g,b,a} with each component going from 0 to 255 and alpha set to 255).
|
|
* @param hex Hex color
|
|
* @category Utils > Color
|
|
*/
|
|
export const hexNumberToRGB = (
|
|
hexNumber: number
|
|
): { r: integer; g: integer; b: integer; a: integer } => {
|
|
return {
|
|
r: (hexNumber >> 16) & 0xff,
|
|
g: (hexNumber >> 8) & 0xff,
|
|
b: hexNumber & 0xff,
|
|
a: 255,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Convert a Hex number to a RGB color array([r,g,b] with each component going from 0 to 255).
|
|
* @param hex Hex color
|
|
* @category Utils > Color
|
|
*/
|
|
export const hexNumberToRGBArray = (
|
|
hexNumber: number
|
|
): [integer, integer, integer] => {
|
|
return [
|
|
(hexNumber >> 16) & 0xff,
|
|
(hexNumber >> 8) & 0xff,
|
|
hexNumber & 0xff,
|
|
];
|
|
};
|
|
|
|
/** @internal */
|
|
export const extractHexString = (str: string): string | null => {
|
|
const matches = str.match(hexStringRegex);
|
|
if (!matches) return null;
|
|
return matches[0];
|
|
};
|
|
|
|
/** @internal */
|
|
export const extractShorthandHexString = (str: string): string | null => {
|
|
const matches = str.match(shorthandHexStringRegex);
|
|
if (!matches) return null;
|
|
return matches[0];
|
|
};
|
|
|
|
/**
|
|
* Get a random integer between 0 and max.
|
|
* @param max The maximum value (inclusive).
|
|
* @category Utils > Math
|
|
*/
|
|
export const random = function (max: float): float {
|
|
if (max <= 0) return 0;
|
|
return Math.floor(Math.random() * (max + 1));
|
|
};
|
|
|
|
/**
|
|
* Get a random integer between min and max.
|
|
* @param min The minimum value (inclusive).
|
|
* @param max The maximum value (inclusive).
|
|
* @category Utils > Math
|
|
*/
|
|
export const randomInRange = function (min: float, max: float): float {
|
|
return min + gdjs.random(max - min); // return min if min >= max
|
|
};
|
|
|
|
/**
|
|
* Get a random float in the range 0 to less than max (inclusive of 0, but not max).
|
|
* @param max The maximum value (exclusive).
|
|
* @category Utils > Math
|
|
*/
|
|
export const randomFloat = function (max: float): float {
|
|
if (max <= 0) return 0;
|
|
return Math.random() * max;
|
|
};
|
|
|
|
/**
|
|
* Get a random float between min and max
|
|
* @param min The minimum value (inclusive).
|
|
* @param max The maximum value (exclusive).
|
|
* @returns {number}
|
|
* @category Utils > Math
|
|
*/
|
|
export const randomFloatInRange = function (min: float, max: float): float {
|
|
return min + gdjs.randomFloat(max - min); // return min if min >= max
|
|
};
|
|
|
|
/**
|
|
* Get a random number between min and max in steps
|
|
* @param min The minimum value (inclusive).
|
|
* @param max The maximum value (inclusive).
|
|
* @param step The interval between each value.
|
|
* @returns {number}
|
|
* @category Utils > Math
|
|
*/
|
|
export const randomWithStep = function (
|
|
min: float,
|
|
max: float,
|
|
step: float
|
|
): float {
|
|
if (step <= 0) return min + gdjs.random(max - min);
|
|
return min + gdjs.random(Math.floor((max - min) / step)) * step; // return min if min >= max
|
|
};
|
|
|
|
/**
|
|
* Convert an angle in degrees to radians.
|
|
* @param angleInDegrees The angle in degrees.
|
|
* @category Utils > Math
|
|
*/
|
|
export const toRad = function (angleInDegrees: float): float {
|
|
return (angleInDegrees / 180) * Math.PI;
|
|
};
|
|
|
|
/**
|
|
* Convert an angle in radians to degrees.
|
|
* @param angleInRadians The angle in radians.
|
|
* @category Utils > Math
|
|
*/
|
|
export const toDegrees = function (angleInRadians: float): float {
|
|
return (angleInRadians * 180) / Math.PI;
|
|
};
|
|
|
|
/**
|
|
* Register a runtime object (class extending {@link gdjs.RuntimeObject}) that can be used in a scene.
|
|
*
|
|
* The name of the type of the object must be complete, with the namespace if any. For
|
|
* example, if you are providing a Text object in the TextObject extension, the full name
|
|
* of the type of the object is "TextObject::Text".
|
|
*
|
|
* @param objectTypeName The name of the type of the Object.
|
|
* @param Ctor The constructor of the Object.
|
|
* @category Core Engine > Object
|
|
*/
|
|
export const registerObject = function (
|
|
objectTypeName: string,
|
|
Ctor: typeof gdjs.RuntimeObject
|
|
): void {
|
|
gdjs.objectsTypes.put(objectTypeName, Ctor);
|
|
};
|
|
|
|
/**
|
|
* Register a runtime behavior (class extending {@link gdjs.RuntimeBehavior}) that can be used by a
|
|
* {@link gdjs.RuntimeObject}.
|
|
*
|
|
* The type of the behavior must be complete, with the namespace of the extension. For
|
|
* example, if you are providing a Draggable behavior in the DraggableBehavior extension,
|
|
* the full name of the type of the behavior is "DraggableBehavior::Draggable".
|
|
*
|
|
* @param behaviorTypeName The name of the type of the behavior.
|
|
* @param Ctor The constructor of the Object.
|
|
* @category Core Engine > Behavior
|
|
*/
|
|
export const registerBehavior = function (
|
|
behaviorTypeName: string,
|
|
Ctor: typeof gdjs.RuntimeBehavior
|
|
): void {
|
|
gdjs.behaviorsTypes.put(behaviorTypeName, Ctor);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when the first {@link gdjs.RuntimeScene} is loaded, after
|
|
* resources loading is done. This can be considered as the "start of the game".
|
|
*
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerFirstRuntimeSceneLoadedCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksFirstRuntimeSceneLoaded.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when a scene is loaded.
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeSceneLoadedCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeSceneLoaded.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called each time a scene is stepped (i.e: at every frame),
|
|
* before events are run.
|
|
*
|
|
* @see {@link gdjs.registerRuntimeScenePostEventsCallback}
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeScenePreEventsCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeScenePreEvents.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called each time a scene is stepped (i.e: at every frame),
|
|
* after events are run and before rendering.
|
|
*
|
|
* This is the recommended way to synchronize state changes with GDevelop's game loop,
|
|
* rather than using browser APIs like `requestAnimationFrame`. The game engine owns
|
|
* the game loop and controls the order of execution, so browser APIs may not sync
|
|
* reliably with frame timing.
|
|
*
|
|
* ## Use case: Implementing trigger-like conditions
|
|
*
|
|
* When creating conditions that should act as "triggers" (true for one frame, then
|
|
* automatically false), use this callback to reset flags at frame end. This ensures
|
|
* the flag will:
|
|
* 1. Remain true throughout the current frame (so conditions can detect it)
|
|
* 2. Be reset to false before the next frame begins
|
|
*
|
|
* ### Why this works reliably
|
|
*
|
|
* JavaScript is single-threaded, and asynchronous operations (promises, setTimeout,
|
|
* etc.) only execute between frames, never during frame execution. This means:
|
|
* - A frame runs completely (behaviors update → events run → this callback → render)
|
|
* - Async operations (like Promise resolutions) run after the frame completes
|
|
* - If an async operation sets a flag to true, it stays true until this callback
|
|
* resets it at the end of the next frame
|
|
*
|
|
* ### Example usage
|
|
* ```js
|
|
* // In your extension initialization. Only do this once.
|
|
* gdjs.registerRuntimeScenePostEventsCallback((runtimeScene) => {
|
|
* // Reset trigger flags after events have been processed
|
|
* myExtension._dataRequestCompleted = false;
|
|
* });
|
|
*
|
|
* // When some async operation completes (probably in an action or somewhere else):
|
|
* doSomething().then((data) => {
|
|
* myExtension._dataRequestCompleted = true; // Will be true for one frame
|
|
* });
|
|
*
|
|
* // or using await:
|
|
* const data = await doSomething();
|
|
* myExtension._dataRequestCompleted = true; // Will be true for one frame
|
|
* ```
|
|
*
|
|
* @param callback The function to be called after events and before rendering each frame.
|
|
* Receives the RuntimeScene as parameter.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeScenePostEventsCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeScenePostEvents.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called each time the in-game editor has stepped (i.e: at every frame)
|
|
* before rendering.
|
|
*
|
|
* @param callback The function to be called after in-game editor has stepped and before rendering each frame.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerInGameEditorPostStepCallback = function (
|
|
callback: InGameEditorCallback
|
|
): void {
|
|
gdjs.callbacksInGameEditorPostStep.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when a scene is paused.
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeScenePausedCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeScenePaused.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when a scene is resumed.
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeSceneResumedCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeSceneResumed.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when a scene unload started. This is
|
|
* before the object deletion and renderer destruction. It is safe to
|
|
* manipulate these. It is **not** safe to release resources as other
|
|
* callbacks might do operations on objects or the scene.
|
|
*
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeSceneUnloadingCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeSceneUnloading.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when a scene is unloaded. The objects
|
|
* and renderer are now destroyed - it is **not** safe to do anything apart
|
|
* from releasing resources.
|
|
*
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeSceneUnloadedCallback = function (
|
|
callback: RuntimeSceneCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeSceneUnloaded.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called when an object is deleted from a scene.
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Object
|
|
*/
|
|
export const registerObjectDeletedFromSceneCallback = function (
|
|
callback: RuntimeSceneRuntimeObjectCallback
|
|
): void {
|
|
gdjs.callbacksObjectDeletedFromScene.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called each time a scene is getting its sync
|
|
* data retrieved (via getNetworkSyncData).
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeSceneGetSyncDataCallback = function (
|
|
callback: RuntimeSceneGetSyncDataCallback
|
|
): void {
|
|
gdjs.callbacksRuntimeSceneGetSyncData.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Register a function to be called each time a scene is getting its sync
|
|
* data updated (via updateFromNetworkSyncData).
|
|
* @param callback The function to be called.
|
|
* @category Core Engine > Scene
|
|
*/
|
|
export const registerRuntimeSceneUpdateFromSyncDataCallback = function (
|
|
callback: RuntimeSceneUpdateFromSyncData
|
|
): void {
|
|
gdjs.callbacksRuntimeSceneUpdateFromSyncData.push(callback);
|
|
};
|
|
|
|
/**
|
|
* Unregister a callback.
|
|
* This should not be used apart from the code generated from extensions
|
|
* events functions, to handle hot-reloading.
|
|
* In any other case, a callback should be registered once, and only once.
|
|
*
|
|
* @internal
|
|
*/
|
|
export const _unregisterCallback = function (callback: unknown): void {
|
|
const filterArrayInPlace = (array: unknown[]) => {
|
|
for (let i = 0; i < array.length; ) {
|
|
if (array[i] === callback) {
|
|
array.splice(i, 1);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
};
|
|
|
|
filterArrayInPlace(callbacksFirstRuntimeSceneLoaded);
|
|
filterArrayInPlace(callbacksRuntimeSceneLoaded);
|
|
filterArrayInPlace(callbacksRuntimeScenePreEvents);
|
|
filterArrayInPlace(callbacksRuntimeScenePostEvents);
|
|
filterArrayInPlace(callbacksInGameEditorPostStep);
|
|
filterArrayInPlace(callbacksRuntimeScenePaused);
|
|
filterArrayInPlace(callbacksRuntimeSceneResumed);
|
|
filterArrayInPlace(callbacksRuntimeSceneUnloading);
|
|
filterArrayInPlace(callbacksRuntimeSceneUnloaded);
|
|
filterArrayInPlace(callbacksObjectDeletedFromScene);
|
|
};
|
|
|
|
/**
|
|
* Keep this function until we're sure now client is using it anymore.
|
|
* @deprecated
|
|
* @private
|
|
*/
|
|
export const registerGlobalCallbacks = function (): void {
|
|
logger.warn(
|
|
"You're calling gdjs.registerGlobalCallbacks. This method is now useless and you must not call it anymore."
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Get the constructor of an object.
|
|
*
|
|
* @param name The name of the type of the object.
|
|
* @category Core Engine > Object
|
|
*/
|
|
export const getObjectConstructor = function (
|
|
name: string
|
|
): typeof gdjs.RuntimeObject {
|
|
if (name !== undefined && gdjs.objectsTypes.containsKey(name))
|
|
return gdjs.objectsTypes.get(name);
|
|
|
|
logger.warn('Object type "' + name + '" was not found.');
|
|
return gdjs.objectsTypes.get(''); //Create a base empty runtime object.
|
|
};
|
|
|
|
/**
|
|
* Get the constructor of a behavior.
|
|
*
|
|
* @param name The name of the type of the behavior.
|
|
* @category Core Engine > Behavior
|
|
*/
|
|
export const getBehaviorConstructor = function (
|
|
name: string
|
|
): typeof gdjs.RuntimeBehavior {
|
|
if (name !== undefined && gdjs.behaviorsTypes.containsKey(name))
|
|
return gdjs.behaviorsTypes.get(name);
|
|
|
|
logger.warn('Behavior type "' + name + '" was not found.');
|
|
return gdjs.behaviorsTypes.get(''); //Create a base empty runtime behavior.
|
|
};
|
|
|
|
/**
|
|
* Create a static array that won't need a new allocation each time it's used.
|
|
* @param owner The owner of the Array.
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const staticArray = function (owner: any): Array<any> {
|
|
owner._staticArray = owner._staticArray || [];
|
|
return owner._staticArray;
|
|
};
|
|
|
|
/**
|
|
* Create a second static array that won't need a new allocation each time it's used.
|
|
* @param owner The owner of the Array.
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const staticArray2 = function (owner: any): Array<any> {
|
|
owner._staticArray2 = owner._staticArray2 || [];
|
|
return owner._staticArray2;
|
|
};
|
|
|
|
/**
|
|
* Create a static object that won't need a new allocation each time it's used.
|
|
* @param owner The owner of the Array.
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const staticObject = function (owner: any): Object {
|
|
owner._staticObject = owner._staticObject || {};
|
|
return owner._staticObject;
|
|
};
|
|
|
|
/**
|
|
* Return a new array of objects that is the concatenation of all the objects passed
|
|
* as parameters.
|
|
* @param objectsLists
|
|
* @returns {Array}
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const objectsListsToArray = function (
|
|
objectsLists: Hashtable<RuntimeObject>
|
|
): Array<RuntimeObject> {
|
|
var lists = gdjs.staticArray(gdjs.objectsListsToArray);
|
|
objectsLists.values(lists);
|
|
|
|
var result: Array<RuntimeObject> = [];
|
|
for (var i = 0; i < lists.length; ++i) {
|
|
var arr = lists[i];
|
|
for (var k = 0; k < arr.length; ++k) {
|
|
result.push(arr[k]);
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Copy the element for the first array into the second array, so that
|
|
* both array contains the same elements.
|
|
* @param src The source array
|
|
* @param dst The destination array
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const copyArray = function <T>(src: Array<T>, dst: Array<T>): void {
|
|
var len = src.length;
|
|
for (var i = 0; i < len; ++i) {
|
|
dst[i] = src[i];
|
|
}
|
|
dst.length = len;
|
|
};
|
|
|
|
interface MakeUUID {
|
|
(): string;
|
|
hex?: string[];
|
|
}
|
|
|
|
/**
|
|
* Generate a UUID v4.
|
|
* @returns The generated UUID.
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const makeUuid = <MakeUUID>function (): string {
|
|
// Fallback to non cryptographically secure UUIDs if not supported
|
|
if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
|
|
const makeMathRandomUuid = (a?: any): string => {
|
|
return a
|
|
? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16)
|
|
: ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(
|
|
/[018]/g,
|
|
makeMathRandomUuid
|
|
);
|
|
};
|
|
|
|
return makeMathRandomUuid();
|
|
}
|
|
|
|
if (!gdjs.makeUuid.hex) {
|
|
gdjs.makeUuid.hex = [];
|
|
|
|
for (var i = 0; i < 256; i++) {
|
|
gdjs.makeUuid.hex[i] = (i < 16 ? '0' : '') + i.toString(16);
|
|
}
|
|
}
|
|
const hex = gdjs.makeUuid.hex;
|
|
|
|
var r = crypto.getRandomValues(new Uint8Array(16));
|
|
r[6] = (r[6] & 0x0f) | 0x40;
|
|
r[8] = (r[8] & 0x3f) | 0x80;
|
|
|
|
return (
|
|
hex[r[0]] +
|
|
hex[r[1]] +
|
|
hex[r[2]] +
|
|
hex[r[3]] +
|
|
'-' +
|
|
hex[r[4]] +
|
|
hex[r[5]] +
|
|
'-' +
|
|
hex[r[6]] +
|
|
hex[r[7]] +
|
|
'-' +
|
|
hex[r[8]] +
|
|
hex[r[9]] +
|
|
'-' +
|
|
hex[r[10]] +
|
|
hex[r[11]] +
|
|
hex[r[12]] +
|
|
hex[r[13]] +
|
|
hex[r[14]] +
|
|
hex[r[15]]
|
|
);
|
|
};
|
|
|
|
/**
|
|
* See https://floating-point-gui.de/errors/comparison/
|
|
* @param a
|
|
* @param b
|
|
* @param epsilon the relative margin error
|
|
* @returns true when a and b are within a relative margin error.
|
|
* @category Utils > JavaScript
|
|
*/
|
|
export const nearlyEqual = (a: float, b: float, epsilon: float): boolean => {
|
|
const absA = Math.abs(a);
|
|
const absB = Math.abs(b);
|
|
const diff = Math.abs(a - b);
|
|
|
|
if (a === b) {
|
|
// shortcut, handles infinities
|
|
return true;
|
|
} else if (a == 0 || b == 0 || absA + absB < Number.EPSILON) {
|
|
// a or b is zero or both are extremely close to it
|
|
// relative error is less meaningful here
|
|
return diff < epsilon * Number.EPSILON;
|
|
} else {
|
|
// use relative error
|
|
return diff / Math.min(absA + absB, Number.MAX_VALUE) < epsilon;
|
|
}
|
|
};
|
|
|
|
const asynchronouslyLoadingLibraryPromises: Array<Promise<any>> = [];
|
|
|
|
/**
|
|
* Register a promise which will be resolved when a third party library has
|
|
* finished loading (and is required to load before launching the game).
|
|
*
|
|
* This method must be called by any library that loads asynchronously.
|
|
* @category Core Engine > Game
|
|
*/
|
|
export const registerAsynchronouslyLoadingLibraryPromise = (
|
|
promise: Promise<any>
|
|
): void => {
|
|
asynchronouslyLoadingLibraryPromises.push(promise);
|
|
};
|
|
|
|
/**
|
|
* @returns a promise resolved when all all third party libraries, which need
|
|
* to be loaded before the game startup, are loaded. If a library fails
|
|
* loading, this will be rejected.
|
|
* @internal
|
|
*/
|
|
export const getAllAsynchronouslyLoadingLibraryPromise = (): Promise<
|
|
any[]
|
|
> => {
|
|
return Promise.all(asynchronouslyLoadingLibraryPromises);
|
|
};
|
|
}
|
|
|
|
// Make sure console.warn and console.error are available.
|
|
console.warn = console.warn || console.log;
|
|
console.error = console.error || console.log;
|