1
0
forked from sent/waves
waves/public/assets/g/clickerheroes/scripts/main-f.js
2025-04-09 17:11:14 -05:00

4252 lines
172 KiB
JavaScript

'use strict';
{
window.DOMHandler = class DOMHandler {
constructor(iRuntime, componentId) {
this._iRuntime = iRuntime;
this._componentId = componentId;
this._hasTickCallback = false;
this._tickCallback = ()=>this.Tick()
}
Attach() {}
PostToRuntime(handler, data, dispatchOpts, transferables) {
this._iRuntime.PostToRuntimeComponent(this._componentId, handler, data, dispatchOpts, transferables)
}
PostToRuntimeAsync(handler, data, dispatchOpts, transferables) {
return this._iRuntime.PostToRuntimeComponentAsync(this._componentId, handler, data, dispatchOpts, transferables)
}
_PostToRuntimeMaybeSync(name, data, dispatchOpts) {
if (this._iRuntime.UsesWorker())
this.PostToRuntime(name, data, dispatchOpts);
else
this._iRuntime._GetLocalRuntime()["_OnMessageFromDOM"]({
"type": "event",
"component": this._componentId,
"handler": name,
"dispatchOpts": dispatchOpts || null,
"data": data,
"responseId": null
})
}
AddRuntimeMessageHandler(handler, func) {
this._iRuntime.AddRuntimeComponentMessageHandler(this._componentId, handler, func)
}
AddRuntimeMessageHandlers(list) {
for (const [handler,func] of list)
this.AddRuntimeMessageHandler(handler, func)
}
GetRuntimeInterface() {
return this._iRuntime
}
GetComponentID() {
return this._componentId
}
_StartTicking() {
if (this._hasTickCallback)
return;
this._iRuntime._AddRAFCallback(this._tickCallback);
this._hasTickCallback = true
}
_StopTicking() {
if (!this._hasTickCallback)
return;
this._iRuntime._RemoveRAFCallback(this._tickCallback);
this._hasTickCallback = false
}
Tick() {}
}
;
window.RateLimiter = class RateLimiter {
constructor(callback, interval) {
this._callback = callback;
this._interval = interval;
this._timerId = -1;
this._lastCallTime = -Infinity;
this._timerCallFunc = ()=>this._OnTimer();
this._ignoreReset = false;
this._canRunImmediate = false
}
SetCanRunImmediate(c) {
this._canRunImmediate = !!c
}
Call() {
if (this._timerId !== -1)
return;
const nowTime = Date.now();
const timeSinceLastCall = nowTime - this._lastCallTime;
const interval = this._interval;
if (timeSinceLastCall >= interval && this._canRunImmediate) {
this._lastCallTime = nowTime;
this._RunCallback()
} else
this._timerId = self.setTimeout(this._timerCallFunc, Math.max(interval - timeSinceLastCall, 4))
}
_RunCallback() {
this._ignoreReset = true;
this._callback();
this._ignoreReset = false
}
Reset() {
if (this._ignoreReset)
return;
this._CancelTimer();
this._lastCallTime = Date.now()
}
_OnTimer() {
this._timerId = -1;
this._lastCallTime = Date.now();
this._RunCallback()
}
_CancelTimer() {
if (this._timerId !== -1) {
self.clearTimeout(this._timerId);
this._timerId = -1
}
}
Release() {
this._CancelTimer();
this._callback = null;
this._timerCallFunc = null
}
}
}
;'use strict';
{
window.DOMElementHandler = class DOMElementHandler extends self.DOMHandler {
constructor(iRuntime, componentId) {
super(iRuntime, componentId);
this._elementMap = new Map;
this._autoAttach = true;
this.AddRuntimeMessageHandlers([["create", e=>this._OnCreate(e)], ["destroy", e=>this._OnDestroy(e)], ["set-visible", e=>this._OnSetVisible(e)], ["update-position", e=>this._OnUpdatePosition(e)], ["update-state", e=>this._OnUpdateState(e)], ["focus", e=>this._OnSetFocus(e)], ["set-css-style", e=>this._OnSetCssStyle(e)], ["set-attribute", e=>this._OnSetAttribute(e)], ["remove-attribute", e=>this._OnRemoveAttribute(e)]]);
this.AddDOMElementMessageHandler("get-element", elem=>elem)
}
SetAutoAttach(e) {
this._autoAttach = !!e
}
AddDOMElementMessageHandler(handler, func) {
this.AddRuntimeMessageHandler(handler, e=>{
const elementId = e["elementId"];
const elem = this._elementMap.get(elementId);
return func(elem, e)
}
)
}
_OnCreate(e) {
const elementId = e["elementId"];
const elem = this.CreateElement(elementId, e);
this._elementMap.set(elementId, elem);
if (!e["isVisible"])
elem.style.display = "none";
const focusElem = this._GetFocusElement(elem);
focusElem.addEventListener("focus", e=>this._OnFocus(elementId));
focusElem.addEventListener("blur", e=>this._OnBlur(elementId));
if (this._autoAttach)
document.body.appendChild(elem)
}
CreateElement(elementId, e) {
throw new Error("required override");
}
DestroyElement(elem) {}
_OnDestroy(e) {
const elementId = e["elementId"];
const elem = this._elementMap.get(elementId);
this.DestroyElement(elem);
if (this._autoAttach)
elem.parentElement.removeChild(elem);
this._elementMap.delete(elementId)
}
PostToRuntimeElement(handler, elementId, data) {
if (!data)
data = {};
data["elementId"] = elementId;
this.PostToRuntime(handler, data)
}
_PostToRuntimeElementMaybeSync(handler, elementId, data) {
if (!data)
data = {};
data["elementId"] = elementId;
this._PostToRuntimeMaybeSync(handler, data)
}
_OnSetVisible(e) {
if (!this._autoAttach)
return;
const elem = this._elementMap.get(e["elementId"]);
elem.style.display = e["isVisible"] ? "" : "none"
}
_OnUpdatePosition(e) {
if (!this._autoAttach)
return;
const elem = this._elementMap.get(e["elementId"]);
elem.style.left = e["left"] + "px";
elem.style.top = e["top"] + "px";
elem.style.width = e["width"] + "px";
elem.style.height = e["height"] + "px";
const fontSize = e["fontSize"];
if (fontSize !== null)
elem.style.fontSize = fontSize + "em"
}
_OnUpdateState(e) {
const elem = this._elementMap.get(e["elementId"]);
this.UpdateState(elem, e)
}
UpdateState(elem, e) {
throw new Error("required override");
}
_GetFocusElement(elem) {
return elem
}
_OnFocus(elementId) {
this.PostToRuntimeElement("elem-focused", elementId)
}
_OnBlur(elementId) {
this.PostToRuntimeElement("elem-blurred", elementId)
}
_OnSetFocus(e) {
const elem = this._GetFocusElement(this._elementMap.get(e["elementId"]));
if (e["focus"])
elem.focus();
else
elem.blur()
}
_OnSetCssStyle(e) {
const elem = this._elementMap.get(e["elementId"]);
elem.style[e["prop"]] = e["val"]
}
_OnSetAttribute(e) {
const elem = this._elementMap.get(e["elementId"]);
elem.setAttribute(e["name"], e["val"])
}
_OnRemoveAttribute(e) {
const elem = this._elementMap.get(e["elementId"]);
elem.removeAttribute(e["name"])
}
GetElementById(elementId) {
return this._elementMap.get(elementId)
}
}
}
;'use strict';
{
const isiOSLike = /(iphone|ipod|ipad|macos|macintosh|mac os x)/i.test(navigator.userAgent);
function AddScript(url) {
if (url.isStringSrc) {
const elem = document.createElement("script");
elem.async = false;
elem.textContent = url.str;
document.head.appendChild(elem)
} else
return new Promise((resolve,reject)=>{
const elem = document.createElement("script");
elem.onload = resolve;
elem.onerror = reject;
elem.async = false;
elem.src = url;
document.head.appendChild(elem)
}
)
}
let tmpAudio = new Audio;
const supportedAudioFormats = {
"audio/webm; codecs=opus": !!tmpAudio.canPlayType("audio/webm; codecs=opus"),
"audio/ogg; codecs=opus": !!tmpAudio.canPlayType("audio/ogg; codecs=opus"),
"audio/webm; codecs=vorbis": !!tmpAudio.canPlayType("audio/webm; codecs=vorbis"),
"audio/ogg; codecs=vorbis": !!tmpAudio.canPlayType("audio/ogg; codecs=vorbis"),
"audio/mp4": !!tmpAudio.canPlayType("audio/mp4"),
"audio/mpeg": !!tmpAudio.canPlayType("audio/mpeg")
};
tmpAudio = null;
async function BlobToString(blob) {
const arrayBuffer = await BlobToArrayBuffer(blob);
const textDecoder = new TextDecoder("utf-8");
return textDecoder.decode(arrayBuffer)
}
function BlobToArrayBuffer(blob) {
return new Promise((resolve,reject)=>{
const fileReader = new FileReader;
fileReader.onload = e=>resolve(e.target.result);
fileReader.onerror = err=>reject(err);
fileReader.readAsArrayBuffer(blob)
}
)
}
const queuedArrayBufferReads = [];
let activeArrayBufferReads = 0;
const MAX_ARRAYBUFFER_READS = 8;
window["RealFile"] = window["File"];
const domHandlerClasses = [];
const runtimeEventHandlers = new Map;
const pendingResponsePromises = new Map;
let nextResponseId = 0;
const runOnStartupFunctions = [];
self.runOnStartup = function runOnStartup(f) {
if (typeof f !== "function")
throw new Error("runOnStartup called without a function");
runOnStartupFunctions.push(f)
}
;
const WEBVIEW_EXPORT_TYPES = new Set(["cordova", "playable-ad", "instant-games"]);
function IsWebViewExportType(exportType) {
return WEBVIEW_EXPORT_TYPES.has(exportType)
}
window.RuntimeInterface = class RuntimeInterface {
constructor(opts) {
this._useWorker = opts.useWorker;
this._messageChannelPort = null;
this._baseUrl = "";
this._scriptFolder = opts.scriptFolder;
this._workerScriptBlobURLs = {};
this._worker = null;
this._localRuntime = null;
this._loadingElem = null;
this._domHandlers = [];
this._runtimeDomHandler = null;
this._canvas = null;
this._jobScheduler = null;
this._rafId = -1;
this._rafFunc = ()=>this._OnRAFCallback();
this._rafCallbacks = [];
this._exportType = opts.exportType;
if (this._useWorker && (typeof OffscreenCanvas === "undefined" || !navigator["userActivation"]))
this._useWorker = false;
if (IsWebViewExportType(this._exportType) && this._useWorker) {
console.warn("[C3 runtime] Worker mode is enabled and supported, but is disabled in WebViews due to crbug.com/923007. Reverting to DOM mode.");
this._useWorker = false
}
this._localFileBlobs = null;
this._localFileStrings = null;
if ((this._exportType === "html5" || this._exportType === "playable-ad") && location.protocol.substr(0, 4) === "file")
alert("Exported games won't work until you upload them. (When running on the file: protocol, browsers block many features from working for security reasons.)");
this.AddRuntimeComponentMessageHandler("runtime", "cordova-fetch-local-file", e=>this._OnCordovaFetchLocalFile(e));
this.AddRuntimeComponentMessageHandler("runtime", "create-job-worker", e=>this._OnCreateJobWorker(e));
if (this._exportType === "cordova")
document.addEventListener("deviceready", ()=>this._Init(opts));
else
this._Init(opts)
}
Release() {
this._CancelAnimationFrame();
if (this._messageChannelPort) {
this._messageChannelPort.onmessage = null;
this._messageChannelPort = null
}
if (this._worker) {
this._worker.terminate();
this._worker = null
}
if (this._localRuntime) {
this._localRuntime.Release();
this._localRuntime = null
}
if (this._canvas) {
this._canvas.parentElement.removeChild(this._canvas);
this._canvas = null
}
}
GetCanvas() {
return this._canvas
}
GetBaseURL() {
return this._baseUrl
}
UsesWorker() {
return this._useWorker
}
GetExportType() {
return this._exportType
}
GetScriptFolder() {
return this._scriptFolder
}
IsiOSCordova() {
return isiOSLike && this._exportType === "cordova"
}
IsiOSWebView() {
return isiOSLike && IsWebViewExportType(this._exportType) || navigator["standalone"]
}
async _Init(opts) {
if (this._exportType === "preview") {
this._loadingElem = document.createElement("div");
this._loadingElem.className = "previewLoadingMessage";
this._loadingElem.textContent = opts.previewLoadingMessage;
document.body.appendChild(this._loadingElem)
}
if (this._exportType === "playable-ad") {
this._localFileBlobs = self["c3_base64files"];
this._localFileStrings = {};
await this._ConvertDataUrisToBlobs();
for (let i = 0, len = opts.engineScripts.length; i < len; ++i) {
const src = opts.engineScripts[i].toLowerCase();
if (this._localFileStrings.hasOwnProperty(src))
opts.engineScripts[i] = {
isStringSrc: true,
str: this._localFileStrings[src]
};
else if (this._localFileBlobs.hasOwnProperty(src))
opts.engineScripts[i] = URL.createObjectURL(this._localFileBlobs[src])
}
}
if (opts.baseUrl)
this._baseUrl = opts.baseUrl;
else {
const origin = location.origin;
this._baseUrl = (origin === "null" ? "file:///" : origin) + location.pathname;
const i = this._baseUrl.lastIndexOf("/");
if (i !== -1)
this._baseUrl = this._baseUrl.substr(0, i + 1)
}
if (opts.workerScripts)
for (const [url,blob] of Object.entries(opts.workerScripts))
this._workerScriptBlobURLs[url] = URL.createObjectURL(blob);
const messageChannel = new MessageChannel;
this._messageChannelPort = messageChannel.port1;
this._messageChannelPort.onmessage = e=>this["_OnMessageFromRuntime"](e.data);
if (window["c3_addPortMessageHandler"])
window["c3_addPortMessageHandler"](e=>this._OnMessageFromDebugger(e));
this._jobScheduler = new self.JobSchedulerDOM(this);
await this._jobScheduler.Init();
this.MaybeForceBodySize();
if (typeof window["StatusBar"] === "object")
window["StatusBar"]["hide"]();
if (typeof window["AndroidFullScreen"] === "object")
window["AndroidFullScreen"]["immersiveMode"]();
if (this._useWorker)
await this._InitWorker(opts, messageChannel.port2);
else
await this._InitDOM(opts, messageChannel.port2)
}
_GetWorkerURL(url) {
if (this._workerScriptBlobURLs.hasOwnProperty(url))
return this._workerScriptBlobURLs[url];
else if (url.endsWith("/workermain.js") && this._workerScriptBlobURLs.hasOwnProperty("workermain.js"))
return this._workerScriptBlobURLs["workermain.js"];
else if (this._exportType === "playable-ad" && this._localFileBlobs.hasOwnProperty(url.toLowerCase()))
return URL.createObjectURL(this._localFileBlobs[url.toLowerCase()]);
else
return url
}
async CreateWorker(url, baseUrl, workerOpts) {
if (url.startsWith("blob:"))
return new Worker(url,workerOpts);
if (this.IsiOSCordova() && location.protocol === "file:") {
const arrayBuffer = await this.CordovaFetchLocalFileAsArrayBuffer(this._scriptFolder + url);
const blob = new Blob([arrayBuffer],{
type: "application/javascript"
});
return new Worker(URL.createObjectURL(blob),workerOpts)
}
const absUrl = new URL(url,baseUrl);
const isCrossOrigin = location.origin !== absUrl.origin;
if (isCrossOrigin) {
const response = await fetch(absUrl);
if (!response.ok)
throw new Error("failed to fetch worker script");
const blob = await response.blob();
return new Worker(URL.createObjectURL(blob),workerOpts)
} else
return new Worker(absUrl,workerOpts)
}
_GetWindowInnerWidth() {
return Math.max(window.innerWidth, 1)
}
_GetWindowInnerHeight() {
return Math.max(window.innerHeight, 1)
}
MaybeForceBodySize() {
if (this.IsiOSWebView()) {
const docStyle = document["documentElement"].style;
const bodyStyle = document["body"].style;
const isPortrait = window.innerWidth < window.innerHeight;
const width = isPortrait ? window["screen"]["width"] : window["screen"]["height"];
const height = isPortrait ? window["screen"]["height"] : window["screen"]["width"];
bodyStyle["height"] = docStyle["height"] = height + "px";
bodyStyle["width"] = docStyle["width"] = width + "px"
}
}
_GetCommonRuntimeOptions(opts) {
return {
"baseUrl": this._baseUrl,
"windowInnerWidth": this._GetWindowInnerWidth(),
"windowInnerHeight": this._GetWindowInnerHeight(),
"devicePixelRatio": window.devicePixelRatio,
"isFullscreen": RuntimeInterface.IsDocumentFullscreen(),
"projectData": opts.projectData,
"previewImageBlobs": window["cr_previewImageBlobs"] || this._localFileBlobs,
"previewProjectFileBlobs": window["cr_previewProjectFileBlobs"],
"exportType": opts.exportType,
"isDebug": self.location.search.indexOf("debug") > -1,
"ife": !!self.ife,
"jobScheduler": this._jobScheduler.GetPortData(),
"supportedAudioFormats": supportedAudioFormats,
"opusWasmScriptUrl": window["cr_opusWasmScriptUrl"] || this._scriptFolder + "opus.wasm.js",
"opusWasmBinaryUrl": window["cr_opusWasmBinaryUrl"] || this._scriptFolder + "opus.wasm.wasm",
"isiOSCordova": this.IsiOSCordova(),
"isiOSWebView": this.IsiOSWebView(),
"isFBInstantAvailable": typeof self["FBInstant"] !== "undefined"
}
}
async _InitWorker(opts, port2) {
const workerMainUrl = this._GetWorkerURL(opts.workerMainUrl);
this._worker = await this.CreateWorker(workerMainUrl, this._baseUrl, {
name: "Runtime"
});
this._canvas = document.createElement("canvas");
this._canvas.style.display = "none";
const offscreenCanvas = this._canvas["transferControlToOffscreen"]();
document.body.appendChild(this._canvas);
window["c3canvas"] = this._canvas;
this._worker.postMessage(Object.assign(this._GetCommonRuntimeOptions(opts), {
"type": "init-runtime",
"isInWorker": true,
"messagePort": port2,
"canvas": offscreenCanvas,
"workerDependencyScripts": opts.workerDependencyScripts || [],
"engineScripts": opts.engineScripts,
"projectScripts": window.cr_allProjectScripts,
"projectScriptsStatus": self["C3_ProjectScriptsStatus"]
}), [port2, offscreenCanvas, ...this._jobScheduler.GetPortTransferables()]);
this._domHandlers = domHandlerClasses.map(C=>new C(this));
this._FindRuntimeDOMHandler();
self["c3_callFunction"] = (name,params)=>this._runtimeDomHandler._InvokeFunctionFromJS(name, params);
if (this._exportType === "preview")
self["goToLastErrorScript"] = ()=>this.PostToRuntimeComponent("runtime", "go-to-last-error-script")
}
async _InitDOM(opts, port2) {
this._canvas = document.createElement("canvas");
this._canvas.style.display = "none";
document.body.appendChild(this._canvas);
window["c3canvas"] = this._canvas;
this._domHandlers = domHandlerClasses.map(C=>new C(this));
this._FindRuntimeDOMHandler();
const engineScripts = opts.engineScripts.map(url=>typeof url === "string" ? (new URL(url,this._baseUrl)).toString() : url);
if (Array.isArray(opts.workerDependencyScripts))
engineScripts.unshift(...opts.workerDependencyScripts);
await Promise.all(engineScripts.map(url=>AddScript(url)));
if (opts.projectScripts && opts.projectScripts.length > 0) {
const scriptsStatus = self["C3_ProjectScriptsStatus"];
try {
await Promise.all(opts.projectScripts.map(e=>AddScript(e[1])));
if (Object.values(scriptsStatus).some(f=>!f)) {
self.setTimeout(()=>this._ReportProjectScriptError(scriptsStatus), 100);
return
}
} catch (err) {
console.error("[Preview] Error loading project scripts: ", err);
self.setTimeout(()=>this._ReportProjectScriptError(scriptsStatus), 100);
return
}
}
if (this._exportType === "preview" && typeof self.C3.ScriptsInEvents !== "object") {
this._RemoveLoadingMessage();
const msg = "Failed to load JavaScript code used in events. Check all your JavaScript code has valid syntax.";
console.error("[C3 runtime] " + msg);
alert(msg);
return
}
const runtimeOpts = Object.assign(this._GetCommonRuntimeOptions(opts), {
"isInWorker": false,
"messagePort": port2,
"canvas": this._canvas,
"runOnStartupFunctions": runOnStartupFunctions
});
this._OnBeforeCreateRuntime();
this._localRuntime = self["C3_CreateRuntime"](runtimeOpts);
await self["C3_InitRuntime"](this._localRuntime, runtimeOpts)
}
_ReportProjectScriptError(scriptsStatus) {
this._RemoveLoadingMessage();
const failedScripts = Object.entries(scriptsStatus).filter(e=>!e[1]).map(e=>e[0]);
const msg = `Failed to load project script '${failedScripts[0]}'. Check all your JavaScript code has valid syntax.`;
console.error("[Preview] " + msg);
alert(msg)
}
_OnBeforeCreateRuntime() {
this._RemoveLoadingMessage()
}
_RemoveLoadingMessage() {
if (this._loadingElem) {
this._loadingElem.parentElement.removeChild(this._loadingElem);
this._loadingElem = null
}
}
async _OnCreateJobWorker(e) {
const outputPort = await this._jobScheduler._CreateJobWorker();
return {
"outputPort": outputPort,
"transferables": [outputPort]
}
}
_GetLocalRuntime() {
if (this._useWorker)
throw new Error("not available in worker mode");
return this._localRuntime
}
PostToRuntimeComponent(component, handler, data, dispatchOpts, transferables) {
this._messageChannelPort.postMessage({
"type": "event",
"component": component,
"handler": handler,
"dispatchOpts": dispatchOpts || null,
"data": data,
"responseId": null
}, transferables)
}
PostToRuntimeComponentAsync(component, handler, data, dispatchOpts, transferables) {
const responseId = nextResponseId++;
const ret = new Promise((resolve,reject)=>{
pendingResponsePromises.set(responseId, {
resolve,
reject
})
}
);
this._messageChannelPort.postMessage({
"type": "event",
"component": component,
"handler": handler,
"dispatchOpts": dispatchOpts || null,
"data": data,
"responseId": responseId
}, transferables);
return ret
}
["_OnMessageFromRuntime"](data) {
const type = data["type"];
if (type === "event")
return this._OnEventFromRuntime(data);
else if (type === "result")
this._OnResultFromRuntime(data);
else if (type === "runtime-ready")
this._OnRuntimeReady();
else if (type === "alert-error") {
this._RemoveLoadingMessage();
alert(data["message"])
} else if (type === "creating-runtime")
this._OnBeforeCreateRuntime();
else
throw new Error(`unknown message '${type}'`);
}
_OnEventFromRuntime(e) {
const component = e["component"];
const handler = e["handler"];
const data = e["data"];
const responseId = e["responseId"];
const handlerMap = runtimeEventHandlers.get(component);
if (!handlerMap) {
console.warn(`[DOM] No event handlers for component '${component}'`);
return
}
const func = handlerMap.get(handler);
if (!func) {
console.warn(`[DOM] No handler '${handler}' for component '${component}'`);
return
}
let ret = null;
try {
ret = func(data)
} catch (err) {
console.error(`Exception in '${component}' handler '${handler}':`, err);
if (responseId !== null)
this._PostResultToRuntime(responseId, false, "" + err);
return
}
if (responseId === null)
return ret;
else if (ret && ret.then)
ret.then(result=>this._PostResultToRuntime(responseId, true, result)).catch(err=>{
console.error(`Rejection from '${component}' handler '${handler}':`, err);
this._PostResultToRuntime(responseId, false, "" + err)
}
);
else
this._PostResultToRuntime(responseId, true, ret)
}
_PostResultToRuntime(responseId, isOk, result) {
let transferables;
if (result && result["transferables"])
transferables = result["transferables"];
this._messageChannelPort.postMessage({
"type": "result",
"responseId": responseId,
"isOk": isOk,
"result": result
}, transferables)
}
_OnResultFromRuntime(data) {
const responseId = data["responseId"];
const isOk = data["isOk"];
const result = data["result"];
const pendingPromise = pendingResponsePromises.get(responseId);
if (isOk)
pendingPromise.resolve(result);
else
pendingPromise.reject(result);
pendingResponsePromises.delete(responseId)
}
AddRuntimeComponentMessageHandler(component, handler, func) {
let handlerMap = runtimeEventHandlers.get(component);
if (!handlerMap) {
handlerMap = new Map;
runtimeEventHandlers.set(component, handlerMap)
}
if (handlerMap.has(handler))
throw new Error(`[DOM] Component '${component}' already has handler '${handler}'`);
handlerMap.set(handler, func)
}
static AddDOMHandlerClass(Class) {
if (domHandlerClasses.includes(Class))
throw new Error("DOM handler already added");
domHandlerClasses.push(Class)
}
_FindRuntimeDOMHandler() {
for (const dh of this._domHandlers)
if (dh.GetComponentID() === "runtime") {
this._runtimeDomHandler = dh;
return
}
throw new Error("cannot find runtime DOM handler");
}
_OnMessageFromDebugger(e) {
this.PostToRuntimeComponent("debugger", "message", e)
}
_OnRuntimeReady() {
for (const h of this._domHandlers)
h.Attach()
}
static IsDocumentFullscreen() {
return !!(document["fullscreenElement"] || document["webkitFullscreenElement"] || document["mozFullScreenElement"])
}
async GetRemotePreviewStatusInfo() {
return await this.PostToRuntimeComponentAsync("runtime", "get-remote-preview-status-info")
}
_AddRAFCallback(f) {
this._rafCallbacks.push(f);
this._RequestAnimationFrame()
}
_RemoveRAFCallback(f) {
const i = this._rafCallbacks.indexOf(f);
if (i === -1)
throw new Error("invalid callback");
this._rafCallbacks.splice(i, 1);
if (!this._rafCallbacks.length)
this._CancelAnimationFrame()
}
_RequestAnimationFrame() {
if (this._rafId === -1 && this._rafCallbacks.length)
this._rafId = requestAnimationFrame(this._rafFunc)
}
_CancelAnimationFrame() {
if (this._rafId !== -1) {
cancelAnimationFrame(this._rafId);
this._rafId = -1
}
}
_OnRAFCallback() {
this._rafId = -1;
for (const f of this._rafCallbacks)
f();
this._RequestAnimationFrame()
}
TryPlayMedia(mediaElem) {
this._runtimeDomHandler.TryPlayMedia(mediaElem)
}
RemovePendingPlay(mediaElem) {
this._runtimeDomHandler.RemovePendingPlay(mediaElem)
}
_PlayPendingMedia() {
this._runtimeDomHandler._PlayPendingMedia()
}
SetSilent(s) {
this._runtimeDomHandler.SetSilent(s)
}
IsAudioFormatSupported(typeStr) {
return !!supportedAudioFormats[typeStr]
}
async _WasmDecodeWebMOpus(arrayBuffer) {
const result = await this.PostToRuntimeComponentAsync("runtime", "opus-decode", {
"arrayBuffer": arrayBuffer
}, null, [arrayBuffer]);
return new Float32Array(result)
}
IsAbsoluteURL(url) {
return /^(?:[a-z]+:)?\/\//.test(url) || url.substr(0, 5) === "data:" || url.substr(0, 5) === "blob:"
}
IsRelativeURL(url) {
return !this.IsAbsoluteURL(url)
}
async _OnCordovaFetchLocalFile(e) {
const filename = e["filename"];
switch (e["as"]) {
case "text":
return await this.CordovaFetchLocalFileAsText(filename);
case "buffer":
return await this.CordovaFetchLocalFileAsArrayBuffer(filename);
default:
throw new Error("unsupported type");
}
}
_GetPermissionAPI() {
const api = window["cordova"] && window["cordova"]["plugins"] && window["cordova"]["plugins"]["permissions"];
if (typeof api !== "object")
throw new Error("Permission API is not loaded");
return api
}
_MapPermissionID(api, permission) {
const permissionID = api[permission];
if (typeof permissionID !== "string")
throw new Error("Invalid permission name");
return permissionID
}
_HasPermission(id) {
const api = this._GetPermissionAPI();
return new Promise((resolve,reject)=>api["checkPermission"](this._MapPermissionID(api, id), status=>resolve(!!status["hasPermission"]), reject))
}
_RequestPermission(id) {
const api = this._GetPermissionAPI();
return new Promise((resolve,reject)=>api["requestPermission"](this._MapPermissionID(api, id), status=>resolve(!!status["hasPermission"]), reject))
}
async RequestPermissions(permissions) {
if (this.GetExportType() !== "cordova")
return true;
if (this.IsiOSCordova())
return true;
for (const id of permissions) {
const alreadyGranted = await this._HasPermission(id);
if (alreadyGranted)
continue;
const granted = await this._RequestPermission(id);
if (granted === false)
return false
}
return true
}
async RequirePermissions(...permissions) {
if (await this.RequestPermissions(permissions) === false)
throw new Error("Permission not granted");
}
CordovaFetchLocalFile(filename) {
const path = window["cordova"]["file"]["applicationDirectory"] + "www/" + filename.toLowerCase();
return new Promise((resolve,reject)=>{
window["resolveLocalFileSystemURL"](path, entry=>{
entry["file"](resolve, reject)
}
, reject)
}
)
}
async CordovaFetchLocalFileAsText(filename) {
const file = await this.CordovaFetchLocalFile(filename);
return await BlobToString(file)
}
_CordovaMaybeStartNextArrayBufferRead() {
if (!queuedArrayBufferReads.length)
return;
if (activeArrayBufferReads >= MAX_ARRAYBUFFER_READS)
return;
activeArrayBufferReads++;
const job = queuedArrayBufferReads.shift();
this._CordovaDoFetchLocalFileAsAsArrayBuffer(job.filename, job.successCallback, job.errorCallback)
}
CordovaFetchLocalFileAsArrayBuffer(filename) {
return new Promise((resolve,reject)=>{
queuedArrayBufferReads.push({
filename: filename,
successCallback: result=>{
activeArrayBufferReads--;
this._CordovaMaybeStartNextArrayBufferRead();
resolve(result)
}
,
errorCallback: err=>{
activeArrayBufferReads--;
this._CordovaMaybeStartNextArrayBufferRead();
reject(err)
}
});
this._CordovaMaybeStartNextArrayBufferRead()
}
)
}
async _CordovaDoFetchLocalFileAsAsArrayBuffer(filename, successCallback, errorCallback) {
try {
const file = await this.CordovaFetchLocalFile(filename);
const arrayBuffer = await BlobToArrayBuffer(file);
successCallback(arrayBuffer)
} catch (err) {
errorCallback(err)
}
}
async _ConvertDataUrisToBlobs() {
const promises = [];
for (const [filename,data] of Object.entries(this._localFileBlobs))
promises.push(this._ConvertDataUriToBlobs(filename, data));
await Promise.all(promises)
}
async _ConvertDataUriToBlobs(filename, data) {
if (typeof data === "object") {
this._localFileBlobs[filename] = new Blob([data["str"]],{
"type": data["type"]
});
this._localFileStrings[filename] = data["str"]
} else {
let blob = await this._FetchDataUri(data);
if (!blob)
blob = this._DataURIToBinaryBlobSync(data);
this._localFileBlobs[filename] = blob
}
}
async _FetchDataUri(dataUri) {
try {
const response = await fetch(dataUri);
return await response.blob()
} catch (err) {
console.warn("Failed to fetch a data: URI. Falling back to a slower workaround. This is probably because the Content Security Policy unnecessarily blocked it. Allow data: URIs in your CSP to avoid this.", err);
return null
}
}
_DataURIToBinaryBlobSync(datauri) {
const o = this._ParseDataURI(datauri);
return this._BinaryStringToBlob(o.data, o.mime_type)
}
_ParseDataURI(datauri) {
const comma = datauri.indexOf(",");
if (comma < 0)
throw new URIError("expected comma in data: uri");
const typepart = datauri.substring(5, comma);
const datapart = datauri.substring(comma + 1);
const typearr = typepart.split(";");
const mimetype = typearr[0] || "";
const encoding1 = typearr[1];
const encoding2 = typearr[2];
let decodeddata;
if (encoding1 === "base64" || encoding2 === "base64")
decodeddata = atob(datapart);
else
decodeddata = decodeURIComponent(datapart);
return {
mime_type: mimetype,
data: decodeddata
}
}
_BinaryStringToBlob(binstr, mime_type) {
let len = binstr.length;
let len32 = len >> 2;
let a8 = new Uint8Array(len);
let a32 = new Uint32Array(a8.buffer,0,len32);
let i, j;
for (i = 0,
j = 0; i < len32; ++i)
a32[i] = binstr.charCodeAt(j++) | binstr.charCodeAt(j++) << 8 | binstr.charCodeAt(j++) << 16 | binstr.charCodeAt(j++) << 24;
let tailLength = len & 3;
while (tailLength--) {
a8[j] = binstr.charCodeAt(j);
++j
}
return new Blob([a8],{
"type": mime_type
})
}
}
}
;'use strict';
{
const RuntimeInterface = self.RuntimeInterface;
function IsCompatibilityMouseEvent(e) {
return e["sourceCapabilities"] && e["sourceCapabilities"]["firesTouchEvents"] || e["originalEvent"] && e["originalEvent"]["sourceCapabilities"] && e["originalEvent"]["sourceCapabilities"]["firesTouchEvents"]
}
const KEY_CODE_ALIASES = new Map([["OSLeft", "MetaLeft"], ["OSRight", "MetaRight"]]);
const DISPATCH_RUNTIME_AND_SCRIPT = {
"dispatchRuntimeEvent": true,
"dispatchUserScriptEvent": true
};
const DISPATCH_SCRIPT_ONLY = {
"dispatchUserScriptEvent": true
};
const DISPATCH_RUNTIME_ONLY = {
"dispatchRuntimeEvent": true
};
function AddStyleSheet(cssUrl) {
return new Promise((resolve,reject)=>{
const styleLink = document.createElement("link");
styleLink.onload = ()=>resolve(styleLink);
styleLink.onerror = err=>reject(err);
styleLink.rel = "stylesheet";
styleLink.href = cssUrl;
document.head.appendChild(styleLink)
}
)
}
function FetchImage(url) {
return new Promise((resolve,reject)=>{
const img = new Image;
img.onload = ()=>resolve(img);
img.onerror = err=>reject(err);
img.src = url
}
)
}
async function BlobToImage(blob) {
const blobUrl = URL.createObjectURL(blob);
try {
return await FetchImage(blobUrl)
} finally {
URL.revokeObjectURL(blobUrl)
}
}
function BlobToString(blob) {
return new Promise((resolve,reject)=>{
let fileReader = new FileReader;
fileReader.onload = e=>resolve(e.target.result);
fileReader.onerror = err=>reject(err);
fileReader.readAsText(blob)
}
)
}
async function BlobToSvgImage(blob, width, height) {
if (!/firefox/i.test(navigator.userAgent))
return await BlobToImage(blob);
let str = await BlobToString(blob);
const parser = new DOMParser;
const doc = parser.parseFromString(str, "image/svg+xml");
const rootElem = doc.documentElement;
if (rootElem.hasAttribute("width") && rootElem.hasAttribute("height")) {
const widthStr = rootElem.getAttribute("width");
const heightStr = rootElem.getAttribute("height");
if (!widthStr.includes("%") && !heightStr.includes("%"))
return await BlobToImage(blob)
}
rootElem.setAttribute("width", width + "px");
rootElem.setAttribute("height", height + "px");
const serializer = new XMLSerializer;
str = serializer.serializeToString(doc);
blob = new Blob([str],{
type: "image/svg+xml"
});
return await BlobToImage(blob)
}
function IsInContentEditable(el) {
do {
if (el.parentNode && el.hasAttribute("contenteditable"))
return true;
el = el.parentNode
} while (el);
return false
}
const canvasOrDocTags = new Set(["canvas", "body", "html"]);
function PreventDefaultOnCanvasOrDoc(e) {
const tagName = e.target.tagName.toLowerCase();
if (canvasOrDocTags.has(tagName))
e.preventDefault()
}
function BlockWheelZoom(e) {
if (e.metaKey || e.ctrlKey)
e.preventDefault()
}
self["C3_GetSvgImageSize"] = async function(blob) {
const img = await BlobToImage(blob);
if (img.width > 0 && img.height > 0)
return [img.width, img.height];
else {
img.style.position = "absolute";
img.style.left = "0px";
img.style.top = "0px";
img.style.visibility = "hidden";
document.body.appendChild(img);
const rc = img.getBoundingClientRect();
document.body.removeChild(img);
return [rc.width, rc.height]
}
}
;
self["C3_RasterSvgImageBlob"] = async function(blob, imageWidth, imageHeight, surfaceWidth, surfaceHeight) {
const img = await BlobToSvgImage(blob, imageWidth, imageHeight);
const canvas = document.createElement("canvas");
canvas.width = surfaceWidth;
canvas.height = surfaceHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, imageWidth, imageHeight);
return canvas
}
;
let isCordovaPaused = false;
document.addEventListener("pause", ()=>isCordovaPaused = true);
document.addEventListener("resume", ()=>isCordovaPaused = false);
function ParentHasFocus() {
try {
return window.parent && window.parent.document.hasFocus()
} catch (err) {
return false
}
}
function KeyboardIsVisible() {
const elem = document.activeElement;
if (!elem)
return false;
const tagName = elem.tagName.toLowerCase();
const inputTypes = new Set(["email", "number", "password", "search", "tel", "text", "url"]);
if (tagName === "textarea")
return true;
if (tagName === "input")
return inputTypes.has(elem.type.toLowerCase() || "text");
return IsInContentEditable(elem)
}
const DOM_COMPONENT_ID = "runtime";
const HANDLER_CLASS = class RuntimeDOMHandler extends self.DOMHandler {
constructor(iRuntime) {
super(iRuntime, DOM_COMPONENT_ID);
this._isFirstSizeUpdate = true;
this._simulatedResizeTimerId = -1;
this._targetOrientation = "any";
this._attachedDeviceOrientationEvent = false;
this._attachedDeviceMotionEvent = false;
this._debugHighlightElem = null;
this._pointerRawUpdateRateLimiter = null;
this._lastPointerRawUpdateEvent = null;
iRuntime.AddRuntimeComponentMessageHandler("canvas", "update-size", e=>this._OnUpdateCanvasSize(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "invoke-download", e=>this._OnInvokeDownload(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "raster-svg-image", e=>this._OnRasterSvgImage(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "get-svg-image-size", e=>this._OnGetSvgImageSize(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "set-target-orientation", e=>this._OnSetTargetOrientation(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "register-sw", ()=>this._OnRegisterSW());
iRuntime.AddRuntimeComponentMessageHandler("runtime", "post-to-debugger", e=>this._OnPostToDebugger(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "go-to-script", e=>this._OnPostToDebugger(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "before-start-ticking", ()=>this._OnBeforeStartTicking());
iRuntime.AddRuntimeComponentMessageHandler("runtime", "debug-highlight", e=>this._OnDebugHighlight(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "enable-device-orientation", ()=>this._AttachDeviceOrientationEvent());
iRuntime.AddRuntimeComponentMessageHandler("runtime", "enable-device-motion", ()=>this._AttachDeviceMotionEvent());
iRuntime.AddRuntimeComponentMessageHandler("runtime", "add-stylesheet", e=>this._OnAddStylesheet(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "alert", e=>this._OnAlert(e));
iRuntime.AddRuntimeComponentMessageHandler("runtime", "hide-cordova-splash", ()=>this._OnHideCordovaSplash());
const allowDefaultContextMenuTagNames = new Set(["input", "textarea", "datalist"]);
window.addEventListener("contextmenu", e=>{
const t = e.target;
const name = t.tagName.toLowerCase();
if (!allowDefaultContextMenuTagNames.has(name) && !IsInContentEditable(t))
e.preventDefault()
}
);
const canvas = iRuntime.GetCanvas();
window.addEventListener("selectstart", PreventDefaultOnCanvasOrDoc);
window.addEventListener("gesturehold", PreventDefaultOnCanvasOrDoc);
canvas.addEventListener("selectstart", PreventDefaultOnCanvasOrDoc);
canvas.addEventListener("gesturehold", PreventDefaultOnCanvasOrDoc);
window.addEventListener("touchstart", PreventDefaultOnCanvasOrDoc, {
"passive": false
});
if (typeof PointerEvent !== "undefined") {
window.addEventListener("pointerdown", PreventDefaultOnCanvasOrDoc, {
"passive": false
});
canvas.addEventListener("pointerdown", PreventDefaultOnCanvasOrDoc)
} else
canvas.addEventListener("touchstart", PreventDefaultOnCanvasOrDoc);
this._mousePointerLastButtons = 0;
window.addEventListener("mousedown", e=>{
if (e.button === 1)
e.preventDefault()
}
);
window.addEventListener("mousewheel", BlockWheelZoom, {
"passive": false
});
window.addEventListener("wheel", BlockWheelZoom, {
"passive": false
});
window.addEventListener("resize", ()=>this._OnWindowResize());
if (iRuntime.IsiOSWebView())
window.addEventListener("focusout", ()=>{
if (!KeyboardIsVisible())
document.scrollingElement.scrollTop = 0
}
);
this._mediaPendingPlay = new Set;
this._mediaRemovedPendingPlay = new WeakSet;
this._isSilent = false
}
_OnBeforeStartTicking() {
if (this._iRuntime.GetExportType() === "cordova") {
document.addEventListener("pause", ()=>this._OnVisibilityChange(true));
document.addEventListener("resume", ()=>this._OnVisibilityChange(false))
} else
document.addEventListener("visibilitychange", ()=>this._OnVisibilityChange(document.hidden));
return {
"isSuspended": !!(document.hidden || isCordovaPaused)
}
}
Attach() {
window.addEventListener("focus", ()=>this._PostRuntimeEvent("window-focus"));
window.addEventListener("blur", ()=>{
this._PostRuntimeEvent("window-blur", {
"parentHasFocus": ParentHasFocus()
});
this._mousePointerLastButtons = 0
}
);
window.addEventListener("fullscreenchange", ()=>this._OnFullscreenChange());
window.addEventListener("webkitfullscreenchange", ()=>this._OnFullscreenChange());
window.addEventListener("mozfullscreenchange", ()=>this._OnFullscreenChange());
window.addEventListener("fullscreenerror", e=>this._OnFullscreenError(e));
window.addEventListener("webkitfullscreenerror", e=>this._OnFullscreenError(e));
window.addEventListener("mozfullscreenerror", e=>this._OnFullscreenError(e));
window.addEventListener("keydown", e=>this._OnKeyEvent("keydown", e));
window.addEventListener("keyup", e=>this._OnKeyEvent("keyup", e));
window.addEventListener("dblclick", e=>this._OnMouseEvent("dblclick", e, DISPATCH_RUNTIME_AND_SCRIPT));
window.addEventListener("wheel", e=>this._OnMouseWheelEvent("wheel", e));
if (typeof PointerEvent !== "undefined") {
window.addEventListener("pointerdown", e=>{
this._HandlePointerDownFocus(e);
this._OnPointerEvent("pointerdown", e)
}
);
if (this._iRuntime.UsesWorker() && typeof window["onpointerrawupdate"] !== "undefined" && self === self.top) {
this._pointerRawUpdateRateLimiter = new self.RateLimiter(()=>this._DoSendPointerRawUpdate(),5);
this._pointerRawUpdateRateLimiter.SetCanRunImmediate(true);
window.addEventListener("pointerrawupdate", e=>this._OnPointerRawUpdate(e))
} else
window.addEventListener("pointermove", e=>this._OnPointerEvent("pointermove", e));
window.addEventListener("pointerup", e=>this._OnPointerEvent("pointerup", e));
window.addEventListener("pointercancel", e=>this._OnPointerEvent("pointercancel", e))
} else {
window.addEventListener("mousedown", e=>{
this._HandlePointerDownFocus(e);
this._OnMouseEventAsPointer("pointerdown", e)
}
);
window.addEventListener("mousemove", e=>this._OnMouseEventAsPointer("pointermove", e));
window.addEventListener("mouseup", e=>this._OnMouseEventAsPointer("pointerup", e));
window.addEventListener("touchstart", e=>{
this._HandlePointerDownFocus(e);
this._OnTouchEvent("pointerdown", e)
}
);
window.addEventListener("touchmove", e=>this._OnTouchEvent("pointermove", e));
window.addEventListener("touchend", e=>this._OnTouchEvent("pointerup", e));
window.addEventListener("touchcancel", e=>this._OnTouchEvent("pointercancel", e))
}
const playFunc = ()=>this._PlayPendingMedia();
window.addEventListener("pointerup", playFunc, true);
window.addEventListener("touchend", playFunc, true);
window.addEventListener("click", playFunc, true);
window.addEventListener("keydown", playFunc, true);
window.addEventListener("gamepadconnected", playFunc, true)
}
_PostRuntimeEvent(name, data) {
this.PostToRuntime(name, data || null, DISPATCH_RUNTIME_ONLY)
}
_GetWindowInnerWidth() {
return this._iRuntime._GetWindowInnerWidth()
}
_GetWindowInnerHeight() {
return this._iRuntime._GetWindowInnerHeight()
}
_OnWindowResize() {
const width = this._GetWindowInnerWidth();
const height = this._GetWindowInnerHeight();
this._PostRuntimeEvent("window-resize", {
"innerWidth": width,
"innerHeight": height,
"devicePixelRatio": window.devicePixelRatio
});
if (this._iRuntime.IsiOSWebView()) {
if (this._simulatedResizeTimerId !== -1)
clearTimeout(this._simulatedResizeTimerId);
this._OnSimulatedResize(width, height, 0)
}
}
_ScheduleSimulatedResize(width, height, count) {
if (this._simulatedResizeTimerId !== -1)
clearTimeout(this._simulatedResizeTimerId);
this._simulatedResizeTimerId = setTimeout(()=>this._OnSimulatedResize(width, height, count), 48)
}
_OnSimulatedResize(originalWidth, originalHeight, count) {
const width = this._GetWindowInnerWidth();
const height = this._GetWindowInnerHeight();
this._simulatedResizeTimerId = -1;
if (width != originalWidth || height != originalHeight)
this._PostRuntimeEvent("window-resize", {
"innerWidth": width,
"innerHeight": height,
"devicePixelRatio": window.devicePixelRatio
});
else if (count < 10)
this._ScheduleSimulatedResize(width, height, count + 1)
}
_OnSetTargetOrientation(e) {
this._targetOrientation = e["targetOrientation"]
}
_TrySetTargetOrientation() {
const orientation = this._targetOrientation;
if (screen["orientation"] && screen["orientation"]["lock"])
screen["orientation"]["lock"](orientation).catch(err=>console.warn("[Construct 3] Failed to lock orientation: ", err));
else
try {
let result = false;
if (screen["lockOrientation"])
result = screen["lockOrientation"](orientation);
else if (screen["webkitLockOrientation"])
result = screen["webkitLockOrientation"](orientation);
else if (screen["mozLockOrientation"])
result = screen["mozLockOrientation"](orientation);
else if (screen["msLockOrientation"])
result = screen["msLockOrientation"](orientation);
if (!result)
console.warn("[Construct 3] Failed to lock orientation")
} catch (err) {
console.warn("[Construct 3] Failed to lock orientation: ", err)
}
}
_OnFullscreenChange() {
const isDocFullscreen = RuntimeInterface.IsDocumentFullscreen();
if (isDocFullscreen && this._targetOrientation !== "any")
this._TrySetTargetOrientation();
this.PostToRuntime("fullscreenchange", {
"isFullscreen": isDocFullscreen,
"innerWidth": this._GetWindowInnerWidth(),
"innerHeight": this._GetWindowInnerHeight()
})
}
_OnFullscreenError(e) {
console.warn("[Construct 3] Fullscreen request failed: ", e);
this.PostToRuntime("fullscreenerror", {
"isFullscreen": RuntimeInterface.IsDocumentFullscreen(),
"innerWidth": this._GetWindowInnerWidth(),
"innerHeight": this._GetWindowInnerHeight()
})
}
_OnVisibilityChange(isHidden) {
if (isHidden)
this._iRuntime._CancelAnimationFrame();
else
this._iRuntime._RequestAnimationFrame();
this.PostToRuntime("visibilitychange", {
"hidden": isHidden
})
}
_OnKeyEvent(name, e) {
if (e.key === "Backspace")
PreventDefaultOnCanvasOrDoc(e);
const code = KEY_CODE_ALIASES.get(e.code) || e.code;
this._PostToRuntimeMaybeSync(name, {
"code": code,
"key": e.key,
"which": e.which,
"repeat": e.repeat,
"altKey": e.altKey,
"ctrlKey": e.ctrlKey,
"metaKey": e.metaKey,
"shiftKey": e.shiftKey,
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT)
}
_OnMouseWheelEvent(name, e) {
this.PostToRuntime(name, {
"clientX": e.clientX,
"clientY": e.clientY,
"pageX": e.pageX,
"pageY": e.pageY,
"deltaX": e.deltaX,
"deltaY": e.deltaY,
"deltaZ": e.deltaZ,
"deltaMode": e.deltaMode,
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT)
}
_OnMouseEvent(name, e, opts) {
if (IsCompatibilityMouseEvent(e))
return;
this._PostToRuntimeMaybeSync(name, {
"button": e.button,
"buttons": e.buttons,
"clientX": e.clientX,
"clientY": e.clientY,
"pageX": e.pageX,
"pageY": e.pageY,
"timeStamp": e.timeStamp
}, opts)
}
_OnMouseEventAsPointer(name, e) {
if (IsCompatibilityMouseEvent(e))
return;
const pointerId = 1;
const lastButtons = this._mousePointerLastButtons;
if (name === "pointerdown" && lastButtons !== 0)
name = "pointermove";
else if (name === "pointerup" && e.buttons !== 0)
name = "pointermove";
this._PostToRuntimeMaybeSync(name, {
"pointerId": pointerId,
"pointerType": "mouse",
"button": e.button,
"buttons": e.buttons,
"lastButtons": lastButtons,
"clientX": e.clientX,
"clientY": e.clientY,
"pageX": e.pageX,
"pageY": e.pageY,
"width": 0,
"height": 0,
"pressure": 0,
"tangentialPressure": 0,
"tiltX": 0,
"tiltY": 0,
"twist": 0,
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT);
this._mousePointerLastButtons = e.buttons;
this._OnMouseEvent(e.type, e, DISPATCH_SCRIPT_ONLY)
}
_OnPointerEvent(name, e) {
if (this._pointerRawUpdateRateLimiter && name !== "pointermove")
this._pointerRawUpdateRateLimiter.Reset();
let lastButtons = 0;
if (e.pointerType === "mouse")
lastButtons = this._mousePointerLastButtons;
this._PostToRuntimeMaybeSync(name, {
"pointerId": e.pointerId,
"pointerType": e.pointerType,
"button": e.button,
"buttons": e.buttons,
"lastButtons": lastButtons,
"clientX": e.clientX,
"clientY": e.clientY,
"pageX": e.pageX,
"pageY": e.pageY,
"width": e.width || 0,
"height": e.height || 0,
"pressure": e.pressure || 0,
"tangentialPressure": e["tangentialPressure"] || 0,
"tiltX": e.tiltX || 0,
"tiltY": e.tiltY || 0,
"twist": e["twist"] || 0,
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT);
if (e.pointerType === "mouse") {
let mouseEventName = "mousemove";
if (name === "pointerdown")
mouseEventName = "mousedown";
else if (name === "pointerup")
mouseEventName = "pointerup";
this._OnMouseEvent(mouseEventName, e, DISPATCH_SCRIPT_ONLY);
this._mousePointerLastButtons = e.buttons
}
}
_OnPointerRawUpdate(e) {
this._lastPointerRawUpdateEvent = e;
this._pointerRawUpdateRateLimiter.Call()
}
_DoSendPointerRawUpdate() {
this._OnPointerEvent("pointermove", this._lastPointerRawUpdateEvent);
this._lastPointerRawUpdateEvent = null
}
_OnTouchEvent(fireName, e) {
for (let i = 0, len = e.changedTouches.length; i < len; ++i) {
const t = e.changedTouches[i];
this._PostToRuntimeMaybeSync(fireName, {
"pointerId": t.identifier,
"pointerType": "touch",
"button": 0,
"buttons": 0,
"lastButtons": 0,
"clientX": t.clientX,
"clientY": t.clientY,
"pageX": t.pageX,
"pageY": t.pageY,
"width": (t["radiusX"] || t["webkitRadiusX"] || 0) * 2,
"height": (t["radiusY"] || t["webkitRadiusY"] || 0) * 2,
"pressure": t["force"] || t["webkitForce"] || 0,
"tangentialPressure": 0,
"tiltX": 0,
"tiltY": 0,
"twist": t["rotationAngle"] || 0,
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT)
}
}
_HandlePointerDownFocus(e) {
if (window !== window.top)
window.focus();
if (this._IsElementCanvasOrDocument(e.target) && document.activeElement && !this._IsElementCanvasOrDocument(document.activeElement))
document.activeElement.blur()
}
_IsElementCanvasOrDocument(elem) {
return !elem || elem === document || elem === window || elem === document.body || elem.tagName.toLowerCase() === "canvas"
}
_AttachDeviceOrientationEvent() {
if (this._attachedDeviceOrientationEvent)
return;
this._attachedDeviceOrientationEvent = true;
window.addEventListener("deviceorientation", e=>this._OnDeviceOrientation(e));
window.addEventListener("deviceorientationabsolute", e=>this._OnDeviceOrientationAbsolute(e))
}
_AttachDeviceMotionEvent() {
if (this._attachedDeviceMotionEvent)
return;
this._attachedDeviceMotionEvent = true;
window.addEventListener("devicemotion", e=>this._OnDeviceMotion(e))
}
_OnDeviceOrientation(e) {
this.PostToRuntime("deviceorientation", {
"absolute": !!e["absolute"],
"alpha": e["alpha"] || 0,
"beta": e["beta"] || 0,
"gamma": e["gamma"] || 0,
"timeStamp": e.timeStamp,
"webkitCompassHeading": e["webkitCompassHeading"],
"webkitCompassAccuracy": e["webkitCompassAccuracy"]
}, DISPATCH_RUNTIME_AND_SCRIPT)
}
_OnDeviceOrientationAbsolute(e) {
this.PostToRuntime("deviceorientationabsolute", {
"absolute": !!e["absolute"],
"alpha": e["alpha"] || 0,
"beta": e["beta"] || 0,
"gamma": e["gamma"] || 0,
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT)
}
_OnDeviceMotion(e) {
let accProp = null;
const acc = e["acceleration"];
if (acc)
accProp = {
"x": acc["x"] || 0,
"y": acc["y"] || 0,
"z": acc["z"] || 0
};
let withGProp = null;
const withG = e["accelerationIncludingGravity"];
if (withG)
withGProp = {
"x": withG["x"] || 0,
"y": withG["y"] || 0,
"z": withG["z"] || 0
};
let rotationRateProp = null;
const rotationRate = e["rotationRate"];
if (rotationRate)
rotationRateProp = {
"alpha": rotationRate["alpha"] || 0,
"beta": rotationRate["beta"] || 0,
"gamma": rotationRate["gamma"] || 0
};
this.PostToRuntime("devicemotion", {
"acceleration": accProp,
"accelerationIncludingGravity": withGProp,
"rotationRate": rotationRateProp,
"interval": e["interval"],
"timeStamp": e.timeStamp
}, DISPATCH_RUNTIME_AND_SCRIPT)
}
_OnUpdateCanvasSize(e) {
const runtimeInterface = this.GetRuntimeInterface();
const canvas = runtimeInterface.GetCanvas();
canvas.style.width = e["styleWidth"] + "px";
canvas.style.height = e["styleHeight"] + "px";
canvas.style.marginLeft = e["marginLeft"] + "px";
canvas.style.marginTop = e["marginTop"] + "px";
runtimeInterface.MaybeForceBodySize();
if (this._isFirstSizeUpdate) {
canvas.style.display = "";
this._isFirstSizeUpdate = false
}
}
_OnInvokeDownload(e) {
const url = e["url"];
const filename = e["filename"];
const a = document.createElement("a");
const body = document.body;
a.textContent = filename;
a.href = url;
a.download = filename;
body.appendChild(a);
a.click();
body.removeChild(a)
}
async _OnRasterSvgImage(e) {
const blob = e["blob"];
const imageWidth = e["imageWidth"];
const imageHeight = e["imageHeight"];
const surfaceWidth = e["surfaceWidth"];
const surfaceHeight = e["surfaceHeight"];
const imageBitmapOpts = e["imageBitmapOpts"];
const canvas = await self["C3_RasterSvgImageBlob"](blob, imageWidth, imageHeight, surfaceWidth, surfaceHeight);
let ret;
if (imageBitmapOpts)
ret = await createImageBitmap(canvas, imageBitmapOpts);
else
ret = await createImageBitmap(canvas);
return {
"imageBitmap": ret,
"transferables": [ret]
}
}
async _OnGetSvgImageSize(e) {
return await self["C3_GetSvgImageSize"](e["blob"])
}
async _OnAddStylesheet(e) {
await AddStyleSheet(e["url"])
}
_PlayPendingMedia() {
const mediaToTryPlay = [...this._mediaPendingPlay];
this._mediaPendingPlay.clear();
if (!this._isSilent)
for (const mediaElem of mediaToTryPlay) {
const playRet = mediaElem.play();
if (playRet)
playRet.catch(err=>{
if (!this._mediaRemovedPendingPlay.has(mediaElem))
this._mediaPendingPlay.add(mediaElem)
}
)
}
}
TryPlayMedia(mediaElem) {
if (typeof mediaElem.play !== "function")
throw new Error("missing play function");
this._mediaRemovedPendingPlay.delete(mediaElem);
let playRet;
try {
playRet = mediaElem.play()
} catch (err) {
this._mediaPendingPlay.add(mediaElem);
return
}
if (playRet)
playRet.catch(err=>{
if (!this._mediaRemovedPendingPlay.has(mediaElem))
this._mediaPendingPlay.add(mediaElem)
}
)
}
RemovePendingPlay(mediaElem) {
this._mediaPendingPlay.delete(mediaElem);
this._mediaRemovedPendingPlay.add(mediaElem)
}
SetSilent(s) {
this._isSilent = !!s
}
_OnHideCordovaSplash() {
if (navigator["splashscreen"] && navigator["splashscreen"]["hide"])
navigator["splashscreen"]["hide"]()
}
_OnDebugHighlight(e) {
const show = e["show"];
if (!show) {
if (this._debugHighlightElem)
this._debugHighlightElem.style.display = "none";
return
}
if (!this._debugHighlightElem) {
this._debugHighlightElem = document.createElement("div");
this._debugHighlightElem.id = "inspectOutline";
document.body.appendChild(this._debugHighlightElem)
}
const elem = this._debugHighlightElem;
elem.style.display = "";
elem.style.left = e["left"] - 1 + "px";
elem.style.top = e["top"] - 1 + "px";
elem.style.width = e["width"] + 2 + "px";
elem.style.height = e["height"] + 2 + "px";
elem.textContent = e["name"]
}
_OnRegisterSW() {
if (window["C3_RegisterSW"])
window["C3_RegisterSW"]()
}
_OnPostToDebugger(data) {
if (!window["c3_postToMessagePort"])
return;
data["from"] = "runtime";
window["c3_postToMessagePort"](data)
}
_InvokeFunctionFromJS(name, params) {
return this.PostToRuntimeAsync("js-invoke-function", {
"name": name,
"params": params
})
}
_OnAlert(e) {
alert(e["message"])
}
}
;
RuntimeInterface.AddDOMHandlerClass(HANDLER_CLASS)
}
;'use strict';
{
const DISPATCH_WORKER_SCRIPT_NAME = "dispatchworker.js";
const JOB_WORKER_SCRIPT_NAME = "jobworker.js";
self.JobSchedulerDOM = class JobSchedulerDOM {
constructor(runtimeInterface) {
this._runtimeInterface = runtimeInterface;
this._baseUrl = runtimeInterface.GetBaseURL();
if (runtimeInterface.GetExportType() === "preview")
this._baseUrl += "c3/workers/";
else
this._baseUrl += runtimeInterface.GetScriptFolder();
this._maxNumWorkers = Math.min(navigator.hardwareConcurrency || 2, 16);
this._dispatchWorker = null;
this._jobWorkers = [];
this._inputPort = null;
this._outputPort = null
}
async Init() {
if (this._hasInitialised)
throw new Error("already initialised");
this._hasInitialised = true;
const dispatchWorkerScriptUrl = this._runtimeInterface._GetWorkerURL(DISPATCH_WORKER_SCRIPT_NAME);
this._dispatchWorker = await this._runtimeInterface.CreateWorker(dispatchWorkerScriptUrl, this._baseUrl, {
name: "DispatchWorker"
});
const messageChannel = new MessageChannel;
this._inputPort = messageChannel.port1;
this._dispatchWorker.postMessage({
"type": "_init",
"in-port": messageChannel.port2
}, [messageChannel.port2]);
this._outputPort = await this._CreateJobWorker()
}
async _CreateJobWorker() {
const number = this._jobWorkers.length;
const jobWorkerScriptUrl = this._runtimeInterface._GetWorkerURL(JOB_WORKER_SCRIPT_NAME);
const jobWorker = await this._runtimeInterface.CreateWorker(jobWorkerScriptUrl, this._baseUrl, {
name: "JobWorker" + number
});
const dispatchChannel = new MessageChannel;
const outputChannel = new MessageChannel;
this._dispatchWorker.postMessage({
"type": "_addJobWorker",
"port": dispatchChannel.port1
}, [dispatchChannel.port1]);
jobWorker.postMessage({
"type": "init",
"number": number,
"dispatch-port": dispatchChannel.port2,
"output-port": outputChannel.port2
}, [dispatchChannel.port2, outputChannel.port2]);
this._jobWorkers.push(jobWorker);
return outputChannel.port1
}
GetPortData() {
return {
"inputPort": this._inputPort,
"outputPort": this._outputPort,
"maxNumWorkers": this._maxNumWorkers
}
}
GetPortTransferables() {
return [this._inputPort, this._outputPort]
}
}
}
;'use strict';
{
if (window["C3_IsSupported"]) {
const enableWorker = false;
window["c3_runtimeInterface"] = new self.RuntimeInterface({
useWorker: enableWorker,
workerMainUrl: "workermain.js",
engineScripts: ["scripts/c3runtime-f.js"],
scriptFolder: "scripts/",
workerDependencyScripts: [],
exportType: "html5"
})
}
}
;'use strict';
{
const DOM_COMPONENT_ID = "touch";
const HANDLER_CLASS = class TouchDOMHandler extends self.DOMHandler {
constructor(iRuntime) {
super(iRuntime, DOM_COMPONENT_ID);
this.AddRuntimeMessageHandler("request-permission", e=>this._OnRequestPermission(e))
}
async _OnRequestPermission(e) {
const type = e["type"];
let result = true;
if (type === 0)
result = await this._RequestOrientationPermission();
else if (type === 1)
result = await this._RequestMotionPermission();
this.PostToRuntime("permission-result", {
"type": type,
"result": result
})
}
async _RequestOrientationPermission() {
if (!self["DeviceOrientationEvent"] || !self["DeviceOrientationEvent"]["requestPermission"])
return true;
try {
const state = await self["DeviceOrientationEvent"]["requestPermission"]();
return state === "granted"
} catch (err) {
console.warn("[Touch] Failed to request orientation permission: ", err);
return false
}
}
async _RequestMotionPermission() {
if (!self["DeviceMotionEvent"] || !self["DeviceMotionEvent"]["requestPermission"])
return true;
try {
const state = await self["DeviceMotionEvent"]["requestPermission"]();
return state === "granted"
} catch (err) {
console.warn("[Touch] Failed to request motion permission: ", err);
return false
}
}
}
;
self.RuntimeInterface.AddDOMHandlerClass(HANDLER_CLASS)
}
;'use strict';
{
const DOM_COMPONENT_ID = "mouse";
const HANDLER_CLASS = class MouseDOMHandler extends self.DOMHandler {
constructor(iRuntime) {
super(iRuntime, DOM_COMPONENT_ID);
this.AddRuntimeMessageHandler("cursor", e=>this._OnChangeCursorStyle(e))
}
_OnChangeCursorStyle(e) {
document.documentElement.style.cursor = e
}
}
;
self.RuntimeInterface.AddDOMHandlerClass(HANDLER_CLASS)
}
;'use strict';
{
const DOM_COMPONENT_ID = "browser";
const HANDLER_CLASS = class BrowserDOMHandler extends self.DOMHandler {
constructor(iRuntime) {
super(iRuntime, DOM_COMPONENT_ID);
this._exportType = "";
this.AddRuntimeMessageHandlers([["get-initial-state", e=>this._OnGetInitialState(e)], ["ready-for-sw-messages", ()=>this._OnReadyForSWMessages()], ["alert", e=>this._OnAlert(e)], ["close", ()=>this._OnClose()], ["set-focus", e=>this._OnSetFocus(e)], ["vibrate", e=>this._OnVibrate(e)], ["lock-orientation", e=>this._OnLockOrientation(e)], ["unlock-orientation", ()=>this._OnUnlockOrientation()], ["navigate", e=>this._OnNavigate(e)], ["request-fullscreen", e=>this._OnRequestFullscreen(e)], ["exit-fullscreen", ()=>this._OnExitFullscreen()], ["set-hash", e=>this._OnSetHash(e)]]);
window.addEventListener("online", ()=>this._OnOnlineStateChanged(true));
window.addEventListener("offline", ()=>this._OnOnlineStateChanged(false));
window.addEventListener("hashchange", ()=>this._OnHashChange());
document.addEventListener("backbutton", ()=>this._OnCordovaBackButton());
if (typeof Windows !== "undefined")
Windows["UI"]["Core"]["SystemNavigationManager"]["getForCurrentView"]().addEventListener("backrequested", e=>this._OnWin10BackRequested(e))
}
_OnGetInitialState(e) {
this._exportType = e["exportType"];
return {
"location": location.toString(),
"isOnline": !!navigator.onLine,
"referrer": document.referrer,
"title": document.title,
"isCookieEnabled": !!navigator.cookieEnabled,
"screenWidth": screen.width,
"screenHeight": screen.height,
"windowOuterWidth": window.outerWidth,
"windowOuterHeight": window.outerHeight,
"isScirraArcade": typeof window["is_scirra_arcade"] !== "undefined"
}
}
_OnReadyForSWMessages() {
if (!window["C3_RegisterSW"] || !window["OfflineClientInfo"])
return;
window["OfflineClientInfo"]["SetMessageCallback"](e=>this.PostToRuntime("sw-message", e["data"]))
}
_OnOnlineStateChanged(isOnline) {
this.PostToRuntime("online-state", {
"isOnline": isOnline
})
}
_OnCordovaBackButton() {
this.PostToRuntime("backbutton")
}
_OnWin10BackRequested(e) {
e["handled"] = true;
this.PostToRuntime("backbutton")
}
GetNWjsWindow() {
if (this._exportType === "nwjs")
return nw["Window"]["get"]();
else
return null
}
_OnAlert(e) {
alert(e["message"])
}
_OnClose() {
if (navigator["app"] && navigator["app"]["exitApp"])
navigator["app"]["exitApp"]();
else if (navigator["device"] && navigator["device"]["exitApp"])
navigator["device"]["exitApp"]();
else
window.close()
}
_OnSetFocus(e) {
const isFocus = e["isFocus"];
if (this._exportType === "nwjs") {
const win = this.GetNWjsWindow();
if (isFocus)
win["focus"]();
else
win["blur"]()
} else if (isFocus)
window.focus();
else
window.blur()
}
_OnVibrate(e) {
if (navigator["vibrate"])
navigator["vibrate"](e["pattern"])
}
_OnLockOrientation(e) {
const orientation = e["orientation"];
if (screen["orientation"] && screen["orientation"]["lock"])
screen["orientation"]["lock"](orientation).catch(err=>console.warn("[Construct 3] Failed to lock orientation: ", err));
else
try {
let result = false;
if (screen["lockOrientation"])
result = screen["lockOrientation"](orientation);
else if (screen["webkitLockOrientation"])
result = screen["webkitLockOrientation"](orientation);
else if (screen["mozLockOrientation"])
result = screen["mozLockOrientation"](orientation);
else if (screen["msLockOrientation"])
result = screen["msLockOrientation"](orientation);
if (!result)
console.warn("[Construct 3] Failed to lock orientation")
} catch (err) {
console.warn("[Construct 3] Failed to lock orientation: ", err)
}
}
_OnUnlockOrientation() {
try {
if (screen["orientation"] && screen["orientation"]["unlock"])
screen["orientation"]["unlock"]();
else if (screen["unlockOrientation"])
screen["unlockOrientation"]();
else if (screen["webkitUnlockOrientation"])
screen["webkitUnlockOrientation"]();
else if (screen["mozUnlockOrientation"])
screen["mozUnlockOrientation"]();
else if (screen["msUnlockOrientation"])
screen["msUnlockOrientation"]()
} catch (err) {}
}
_OnNavigate(e) {
const type = e["type"];
if (type === "back")
if (navigator["app"] && navigator["app"]["backHistory"])
navigator["app"]["backHistory"]();
else
window.back();
else if (type === "forward")
window.forward();
else if (type === "home")
window.home();
else if (type === "reload")
location.reload();
else if (type === "url") {
const url = e["url"];
const target = e["target"];
const exportType = e["exportType"];
if (exportType === "windows-uwp" && typeof Windows !== "undefined")
Windows["System"]["Launcher"]["launchUriAsync"](new Windows["Foundation"]["Uri"](url));
else if (self["cordova"] && self["cordova"]["InAppBrowser"])
self["cordova"]["InAppBrowser"]["open"](url, "_system");
else if (exportType === "preview")
window.open(url, "_blank");
else if (!this._isScirraArcade)
if (target === 2)
window.top.location = url;
else if (target === 1)
window.parent.location = url;
else
window.location = url
} else if (type === "new-window") {
const url = e["url"];
const tag = e["tag"];
const exportType = e["exportType"];
if (exportType === "windows-uwp" && typeof Windows !== "undefined")
Windows["System"]["Launcher"]["launchUriAsync"](new Windows["Foundation"]["Uri"](url));
else if (self["cordova"] && self["cordova"]["InAppBrowser"])
self["cordova"]["InAppBrowser"]["open"](url, "_system");
else
window.open(url, tag)
}
}
_OnRequestFullscreen(e) {
const opts = {
"navigationUI": "auto"
};
const navUI = e["navUI"];
if (navUI === 1)
opts["navigationUI"] = "hide";
else if (navUI === 2)
opts["navigationUI"] = "show";
const elem = document.documentElement;
if (elem["requestFullscreen"])
elem["requestFullscreen"](opts);
else if (elem["mozRequestFullScreen"])
elem["mozRequestFullScreen"](opts);
else if (elem["msRequestFullscreen"])
elem["msRequestFullscreen"](opts);
else if (elem["webkitRequestFullScreen"])
if (typeof Element["ALLOW_KEYBOARD_INPUT"] !== "undefined")
elem["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"]);
else
elem["webkitRequestFullScreen"]()
}
_OnExitFullscreen() {
if (document["exitFullscreen"])
document["exitFullscreen"]();
else if (document["mozCancelFullScreen"])
document["mozCancelFullScreen"]();
else if (document["msExitFullscreen"])
document["msExitFullscreen"]();
else if (document["webkitCancelFullScreen"])
document["webkitCancelFullScreen"]()
}
_OnSetHash(e) {
location.hash = e["hash"]
}
_OnHashChange() {
this.PostToRuntime("hashchange", {
"location": location.toString()
})
}
}
;
self.RuntimeInterface.AddDOMHandlerClass(HANDLER_CLASS)
}
;'use strict';
{
const R_TO_D = 180 / Math.PI;
const DOM_COMPONENT_ID = "audio";
self.AudioDOMHandler = class AudioDOMHandler extends self.DOMHandler {
constructor(iRuntime) {
super(iRuntime, DOM_COMPONENT_ID);
this._audioContext = null;
this._destinationNode = null;
this._hasUnblocked = false;
this._hasAttachedUnblockEvents = false;
this._unblockFunc = ()=>this._UnblockAudioContext();
this._audioBuffers = [];
this._audioInstances = [];
this._lastAudioInstance = null;
this._lastPlayedTag = "";
this._lastTickCount = -1;
this._pendingTags = new Map;
this._masterVolume = 1;
this._isSilent = false;
this._timeScaleMode = 0;
this._timeScale = 1;
this._gameTime = 0;
this._panningModel = "HRTF";
this._distanceModel = "inverse";
this._refDistance = 600;
this._maxDistance = 1E4;
this._rolloffFactor = 1;
this._playMusicAsSound = false;
this._hasAnySoftwareDecodedMusic = false;
this._supportsWebMOpus = this._iRuntime.IsAudioFormatSupported("audio/webm; codecs=opus");
this._effects = new Map;
this._analysers = new Set;
this._isPendingPostFxState = false;
this._microphoneTag = "";
this._microphoneSource = null;
self["C3Audio_OnMicrophoneStream"] = (localMediaStream,tag)=>this._OnMicrophoneStream(localMediaStream, tag);
this._destMediaStreamNode = null;
self["C3Audio_GetOutputStream"] = ()=>this._OnGetOutputStream();
self["C3Audio_DOMInterface"] = this;
this.AddRuntimeMessageHandlers([["create-audio-context", e=>this._CreateAudioContext(e)], ["play", e=>this._Play(e)], ["stop", e=>this._Stop(e)], ["stop-all", ()=>this._StopAll()], ["set-paused", e=>this._SetPaused(e)], ["set-volume", e=>this._SetVolume(e)], ["fade-volume", e=>this._FadeVolume(e)], ["set-master-volume", e=>this._SetMasterVolume(e)], ["set-muted", e=>this._SetMuted(e)], ["set-silent", e=>this._SetSilent(e)], ["set-looping", e=>this._SetLooping(e)], ["set-playback-rate", e=>this._SetPlaybackRate(e)], ["seek", e=>this._Seek(e)], ["preload", e=>this._Preload(e)], ["unload", e=>this._Unload(e)], ["unload-all", ()=>this._UnloadAll()], ["set-suspended", e=>this._SetSuspended(e)], ["add-effect", e=>this._AddEffect(e)], ["set-effect-param", e=>this._SetEffectParam(e)], ["remove-effects", e=>this._RemoveEffects(e)], ["tick", e=>this._OnTick(e)], ["load-state", e=>this._OnLoadState(e)]])
}
async _CreateAudioContext(e) {
if (e["isiOSCordova"])
this._playMusicAsSound = true;
this._timeScaleMode = e["timeScaleMode"];
this._panningModel = ["equalpower", "HRTF", "soundfield"][e["panningModel"]];
this._distanceModel = ["linear", "inverse", "exponential"][e["distanceModel"]];
this._refDistance = e["refDistance"];
this._maxDistance = e["maxDistance"];
this._rolloffFactor = e["rolloffFactor"];
const opts = {
"latencyHint": e["latencyHint"]
};
if (typeof AudioContext !== "undefined")
this._audioContext = new AudioContext(opts);
else if (typeof webkitAudioContext !== "undefined")
this._audioContext = new webkitAudioContext(opts);
else
throw new Error("Web Audio API not supported");
this._AttachUnblockEvents();
this._audioContext.onstatechange = ()=>{
if (this._audioContext.state !== "running")
this._AttachUnblockEvents()
}
;
this._destinationNode = this._audioContext["createGain"]();
this._destinationNode["connect"](this._audioContext["destination"]);
const listenerPos = e["listenerPos"];
this._audioContext["listener"]["setPosition"](listenerPos[0], listenerPos[1], listenerPos[2]);
this._audioContext["listener"]["setOrientation"](0, 0, 1, 0, -1, 0);
self["C3_GetAudioContextCurrentTime"] = ()=>this.GetAudioCurrentTime();
try {
await Promise.all(e["preloadList"].map(o=>this._GetAudioBuffer(o["originalUrl"], o["url"], o["type"], false)))
} catch (err) {
console.error("[Construct 3] Preloading sounds failed: ", err)
}
return {
"sampleRate": this._audioContext["sampleRate"]
}
}
_AttachUnblockEvents() {
if (this._hasAttachedUnblockEvents)
return;
this._hasUnblocked = false;
window.addEventListener("pointerup", this._unblockFunc, true);
window.addEventListener("touchend", this._unblockFunc, true);
window.addEventListener("click", this._unblockFunc, true);
window.addEventListener("keydown", this._unblockFunc, true);
this._hasAttachedUnblockEvents = true
}
_DetachUnblockEvents() {
if (!this._hasAttachedUnblockEvents)
return;
this._hasUnblocked = true;
window.removeEventListener("pointerup", this._unblockFunc, true);
window.removeEventListener("touchend", this._unblockFunc, true);
window.removeEventListener("click", this._unblockFunc, true);
window.removeEventListener("keydown", this._unblockFunc, true);
this._hasAttachedUnblockEvents = false
}
_UnblockAudioContext() {
if (this._hasUnblocked)
return;
const audioContext = this._audioContext;
if (audioContext["state"] === "suspended" && audioContext["resume"])
audioContext["resume"]();
const buffer = audioContext["createBuffer"](1, 220, 22050);
const source = audioContext["createBufferSource"]();
source["buffer"] = buffer;
source["connect"](audioContext["destination"]);
source["start"](0);
if (audioContext["state"] === "running")
this._DetachUnblockEvents()
}
GetAudioContext() {
return this._audioContext
}
GetAudioCurrentTime() {
return this._audioContext["currentTime"]
}
GetDestinationNode() {
return this._destinationNode
}
GetDestinationForTag(tag) {
const fxChain = this._effects.get(tag.toLowerCase());
if (fxChain)
return fxChain[0].GetInputNode();
else
return this.GetDestinationNode()
}
AddEffectForTag(tag, effect) {
tag = tag.toLowerCase();
let fxChain = this._effects.get(tag);
if (!fxChain) {
fxChain = [];
this._effects.set(tag, fxChain)
}
effect._SetIndex(fxChain.length);
effect._SetTag(tag);
fxChain.push(effect);
this._ReconnectEffects(tag)
}
_ReconnectEffects(tag) {
let destNode = this.GetDestinationNode();
const fxChain = this._effects.get(tag);
if (fxChain && fxChain.length) {
destNode = fxChain[0].GetInputNode();
for (let i = 0, len = fxChain.length; i < len; ++i) {
const n = fxChain[i];
if (i + 1 === len)
n.ConnectTo(this.GetDestinationNode());
else
n.ConnectTo(fxChain[i + 1].GetInputNode())
}
}
for (const ai of this.audioInstancesByTag(tag))
ai.Reconnect(destNode);
if (this._microphoneSource && this._microphoneTag === tag) {
this._microphoneSource["disconnect"]();
this._microphoneSource["connect"](destNode)
}
}
GetMasterVolume() {
return this._masterVolume
}
IsSilent() {
return this._isSilent
}
GetTimeScaleMode() {
return this._timeScaleMode
}
GetTimeScale() {
return this._timeScale
}
GetGameTime() {
return this._gameTime
}
IsPlayMusicAsSound() {
return this._playMusicAsSound
}
SupportsWebMOpus() {
return this._supportsWebMOpus
}
_SetHasAnySoftwareDecodedMusic() {
this._hasAnySoftwareDecodedMusic = true
}
GetPanningModel() {
return this._panningModel
}
GetDistanceModel() {
return this._distanceModel
}
GetReferenceDistance() {
return this._refDistance
}
GetMaxDistance() {
return this._maxDistance
}
GetRolloffFactor() {
return this._rolloffFactor
}
DecodeAudioData(audioData, needsSoftwareDecode) {
if (needsSoftwareDecode)
return this._iRuntime._WasmDecodeWebMOpus(audioData).then(rawAudio=>{
const audioBuffer = this._audioContext["createBuffer"](1, rawAudio.length, 48E3);
const channelBuffer = audioBuffer["getChannelData"](0);
channelBuffer.set(rawAudio);
return audioBuffer
}
);
else
return new Promise((resolve,reject)=>{
this._audioContext["decodeAudioData"](audioData, resolve, reject)
}
)
}
TryPlayMedia(mediaElem) {
this._iRuntime.TryPlayMedia(mediaElem)
}
RemovePendingPlay(mediaElem) {
this._iRuntime.RemovePendingPlay(mediaElem)
}
ReleaseInstancesForBuffer(buffer) {
let j = 0;
for (let i = 0, len = this._audioInstances.length; i < len; ++i) {
const a = this._audioInstances[i];
this._audioInstances[j] = a;
if (a.GetBuffer() === buffer)
a.Release();
else
++j
}
this._audioInstances.length = j
}
ReleaseAllMusicBuffers() {
let j = 0;
for (let i = 0, len = this._audioBuffers.length; i < len; ++i) {
const b = this._audioBuffers[i];
this._audioBuffers[j] = b;
if (b.IsMusic())
b.Release();
else
++j
}
this._audioBuffers.length = j
}
*audioInstancesByTag(tag) {
if (tag)
for (const ai of this._audioInstances) {
if (self.AudioDOMHandler.EqualsNoCase(ai.GetTag(), tag))
yield ai
}
else if (this._lastAudioInstance && !this._lastAudioInstance.HasEnded())
yield this._lastAudioInstance
}
async _GetAudioBuffer(originalUrl, url, type, isMusic, dontCreate) {
for (const ab of this._audioBuffers)
if (ab.GetUrl() === url) {
await ab.Load();
return ab
}
if (dontCreate)
return null;
if (isMusic && (this._playMusicAsSound || this._hasAnySoftwareDecodedMusic))
this.ReleaseAllMusicBuffers();
const ret = self.C3AudioBuffer.Create(this, originalUrl, url, type, isMusic);
this._audioBuffers.push(ret);
await ret.Load();
return ret
}
async _GetAudioInstance(originalUrl, url, type, tag, isMusic) {
for (const ai of this._audioInstances)
if (ai.GetUrl() === url && (ai.CanBeRecycled() || isMusic)) {
ai.SetTag(tag);
return ai
}
const buffer = await this._GetAudioBuffer(originalUrl, url, type, isMusic);
const ret = buffer.CreateInstance(tag);
this._audioInstances.push(ret);
return ret
}
_AddPendingTag(tag) {
let info = this._pendingTags.get(tag);
if (!info) {
let resolve = null;
const promise = new Promise(r=>resolve = r);
info = {
pendingCount: 0,
promise,
resolve
};
this._pendingTags.set(tag, info)
}
info.pendingCount++
}
_RemovePendingTag(tag) {
const info = this._pendingTags.get(tag);
if (!info)
throw new Error("expected pending tag");
info.pendingCount--;
if (info.pendingCount === 0) {
info.resolve();
this._pendingTags.delete(tag)
}
}
TagReady(tag) {
if (!tag)
tag = this._lastPlayedTag;
const info = this._pendingTags.get(tag);
if (info)
return info.promise;
else
return Promise.resolve()
}
_MaybeStartTicking() {
if (this._analysers.size > 0) {
this._StartTicking();
return
}
for (const ai of this._audioInstances)
if (ai.IsActive()) {
this._StartTicking();
return
}
}
Tick() {
for (const a of this._analysers)
a.Tick();
const currentTime = this.GetAudioCurrentTime();
for (const ai of this._audioInstances)
ai.Tick(currentTime);
const instStates = this._audioInstances.filter(a=>a.IsActive()).map(a=>a.GetState());
this.PostToRuntime("state", {
"tickCount": this._lastTickCount,
"audioInstances": instStates,
"analysers": [...this._analysers].map(a=>a.GetData())
});
if (instStates.length === 0 && this._analysers.size === 0)
this._StopTicking()
}
PostTrigger(type, tag, aiid) {
this.PostToRuntime("trigger", {
"type": type,
"tag": tag,
"aiid": aiid
})
}
async _Play(e) {
const originalUrl = e["originalUrl"];
const url = e["url"];
const type = e["type"];
const isMusic = e["isMusic"];
const tag = e["tag"];
const isLooping = e["isLooping"];
const volume = e["vol"];
const position = e["pos"];
const panning = e["panning"];
let startTime = e["off"];
if (startTime > 0 && !e["trueClock"])
if (this._audioContext["getOutputTimestamp"]) {
const outputTimestamp = this._audioContext["getOutputTimestamp"]();
startTime = startTime - outputTimestamp["performanceTime"] / 1E3 + outputTimestamp["contextTime"]
} else
startTime = startTime - performance.now() / 1E3 + this._audioContext["currentTime"];
this._lastPlayedTag = tag;
this._AddPendingTag(tag);
try {
this._lastAudioInstance = await this._GetAudioInstance(originalUrl, url, type, tag, isMusic);
if (panning) {
this._lastAudioInstance.SetPannerEnabled(true);
this._lastAudioInstance.SetPan(panning["x"], panning["y"], panning["angle"], panning["innerAngle"], panning["outerAngle"], panning["outerGain"]);
if (panning.hasOwnProperty("uid"))
this._lastAudioInstance.SetUID(panning["uid"])
} else
this._lastAudioInstance.SetPannerEnabled(false);
this._lastAudioInstance.Play(isLooping, volume, position, startTime)
} catch (err) {
console.error("[Construct 3] Audio: error starting playback: ", err);
return
} finally {
this._RemovePendingTag(tag)
}
this._StartTicking()
}
_Stop(e) {
const tag = e["tag"];
for (const ai of this.audioInstancesByTag(tag))
ai.Stop()
}
_StopAll() {
for (const ai of this._audioInstances)
ai.Stop()
}
_SetPaused(e) {
const tag = e["tag"];
const paused = e["paused"];
for (const ai of this.audioInstancesByTag(tag))
if (paused)
ai.Pause();
else
ai.Resume();
this._MaybeStartTicking()
}
_SetVolume(e) {
const tag = e["tag"];
const vol = e["vol"];
for (const ai of this.audioInstancesByTag(tag))
ai.SetVolume(vol)
}
async _FadeVolume(e) {
const tag = e["tag"];
const vol = e["vol"];
const duration = e["duration"];
const stopOnEnd = e["stopOnEnd"];
await this.TagReady(tag);
for (const ai of this.audioInstancesByTag(tag))
ai.FadeVolume(vol, duration, stopOnEnd);
this._MaybeStartTicking()
}
_SetMasterVolume(e) {
this._masterVolume = e["vol"];
for (const ai of this._audioInstances)
ai._UpdateVolume()
}
_SetMuted(e) {
const tag = e["tag"];
const isMuted = e["isMuted"];
for (const ai of this.audioInstancesByTag(tag))
ai.SetMuted(isMuted)
}
_SetSilent(e) {
this._isSilent = e["isSilent"];
this._iRuntime.SetSilent(this._isSilent);
for (const ai of this._audioInstances)
ai._UpdateMuted()
}
_SetLooping(e) {
const tag = e["tag"];
const isLooping = e["isLooping"];
for (const ai of this.audioInstancesByTag(tag))
ai.SetLooping(isLooping)
}
async _SetPlaybackRate(e) {
const tag = e["tag"];
const rate = e["rate"];
await this.TagReady(tag);
for (const ai of this.audioInstancesByTag(tag))
ai.SetPlaybackRate(rate)
}
async _Seek(e) {
const tag = e["tag"];
const pos = e["pos"];
await this.TagReady(tag);
for (const ai of this.audioInstancesByTag(tag))
ai.Seek(pos)
}
async _Preload(e) {
const originalUrl = e["originalUrl"];
const url = e["url"];
const type = e["type"];
const isMusic = e["isMusic"];
try {
await this._GetAudioInstance(originalUrl, url, type, "", isMusic)
} catch (err) {
console.error("[Construct 3] Audio: error preloading: ", err)
}
}
async _Unload(e) {
const url = e["url"];
const type = e["type"];
const isMusic = e["isMusic"];
const buffer = await this._GetAudioBuffer("", url, type, isMusic, true);
if (!buffer)
return;
buffer.Release();
const i = this._audioBuffers.indexOf(buffer);
if (i !== -1)
this._audioBuffers.splice(i, 1)
}
_UnloadAll() {
for (const buffer of this._audioBuffers)
buffer.Release();
this._audioBuffers.length = 0
}
_SetSuspended(e) {
const isSuspended = e["isSuspended"];
if (!isSuspended && this._audioContext["resume"])
this._audioContext["resume"]();
for (const ai of this._audioInstances)
ai.SetSuspended(isSuspended);
if (isSuspended && this._audioContext["suspend"])
this._audioContext["suspend"]()
}
_OnTick(e) {
this._timeScale = e["timeScale"];
this._gameTime = e["gameTime"];
this._lastTickCount = e["tickCount"];
if (this._timeScaleMode !== 0)
for (const ai of this._audioInstances)
ai._UpdatePlaybackRate();
const listenerPos = e["listenerPos"];
if (listenerPos)
this._audioContext["listener"]["setPosition"](listenerPos[0], listenerPos[1], listenerPos[2]);
for (const instPan of e["instPans"]) {
const uid = instPan["uid"];
for (const ai of this._audioInstances)
if (ai.GetUID() === uid)
ai.SetPanXYA(instPan["x"], instPan["y"], instPan["angle"])
}
}
async _AddEffect(e) {
const type = e["type"];
const tag = e["tag"];
const params = e["params"];
let effect;
if (type === "filter")
effect = new self.C3AudioFilterFX(this,...params);
else if (type === "delay")
effect = new self.C3AudioDelayFX(this,...params);
else if (type === "convolution") {
let buffer = null;
try {
buffer = await this._GetAudioBuffer(e["bufferOriginalUrl"], e["bufferUrl"], e["bufferType"], false)
} catch (err) {
console.log("[Construct 3] Audio: error loading convolution: ", err);
return
}
effect = new self.C3AudioConvolveFX(this,buffer.GetAudioBuffer(),...params);
effect._SetBufferInfo(e["bufferOriginalUrl"], e["bufferUrl"], e["bufferType"])
} else if (type === "flanger")
effect = new self.C3AudioFlangerFX(this,...params);
else if (type === "phaser")
effect = new self.C3AudioPhaserFX(this,...params);
else if (type === "gain")
effect = new self.C3AudioGainFX(this,...params);
else if (type === "tremolo")
effect = new self.C3AudioTremoloFX(this,...params);
else if (type === "ringmod")
effect = new self.C3AudioRingModFX(this,...params);
else if (type === "distortion")
effect = new self.C3AudioDistortionFX(this,...params);
else if (type === "compressor")
effect = new self.C3AudioCompressorFX(this,...params);
else if (type === "analyser")
effect = new self.C3AudioAnalyserFX(this,...params);
else
throw new Error("invalid effect type");
this.AddEffectForTag(tag, effect);
this._PostUpdatedFxState()
}
_SetEffectParam(e) {
const tag = e["tag"];
const index = e["index"];
const param = e["param"];
const value = e["value"];
const ramp = e["ramp"];
const time = e["time"];
const fxChain = this._effects.get(tag);
if (!fxChain || index < 0 || index >= fxChain.length)
return;
fxChain[index].SetParam(param, value, ramp, time);
this._PostUpdatedFxState()
}
_RemoveEffects(e) {
const tag = e["tag"].toLowerCase();
const fxChain = this._effects.get(tag);
if (!fxChain || !fxChain.length)
return;
for (const effect of fxChain)
effect.Release();
this._effects.delete(tag);
this._ReconnectEffects(tag)
}
_AddAnalyser(analyser) {
this._analysers.add(analyser);
this._MaybeStartTicking()
}
_RemoveAnalyser(analyser) {
this._analysers.delete(analyser)
}
_PostUpdatedFxState() {
if (this._isPendingPostFxState)
return;
this._isPendingPostFxState = true;
Promise.resolve().then(()=>this._DoPostUpdatedFxState())
}
_DoPostUpdatedFxState() {
const fxstate = {};
for (const [tag,fxChain] of this._effects)
fxstate[tag] = fxChain.map(e=>e.GetState());
this.PostToRuntime("fxstate", {
"fxstate": fxstate
});
this._isPendingPostFxState = false
}
async _OnLoadState(e) {
const saveLoadMode = e["saveLoadMode"];
if (saveLoadMode !== 3)
for (const ai of this._audioInstances) {
if (ai.IsMusic() && saveLoadMode === 1)
continue;
if (!ai.IsMusic() && saveLoadMode === 2)
continue;
ai.Stop()
}
for (const fxChain of this._effects.values())
for (const effect of fxChain)
effect.Release();
this._effects.clear();
this._timeScale = e["timeScale"];
this._gameTime = e["gameTime"];
const listenerPos = e["listenerPos"];
this._audioContext["listener"]["setPosition"](listenerPos[0], listenerPos[1], listenerPos[2]);
this._isSilent = e["isSilent"];
this._iRuntime.SetSilent(this._isSilent);
this._masterVolume = e["masterVolume"];
const promises = [];
for (const fxChainData of Object.values(e["effects"]))
promises.push(Promise.all(fxChainData.map(d=>this._AddEffect(d))));
await Promise.all(promises);
await Promise.all(e["playing"].map(d=>this._LoadAudioInstance(d, saveLoadMode)));
this._MaybeStartTicking()
}
async _LoadAudioInstance(d, saveLoadMode) {
if (saveLoadMode === 3)
return;
const originalUrl = d["bufferOriginalUrl"];
const url = d["bufferUrl"];
const type = d["bufferType"];
const isMusic = d["isMusic"];
const tag = d["tag"];
const isLooping = d["isLooping"];
const volume = d["volume"];
const position = d["playbackTime"];
if (isMusic && saveLoadMode === 1)
return;
if (!isMusic && saveLoadMode === 2)
return;
let ai = null;
try {
ai = await this._GetAudioInstance(originalUrl, url, type, tag, isMusic)
} catch (err) {
console.error("[Construct 3] Audio: error loading audio state: ", err);
return
}
ai.LoadPanState(d["pan"]);
ai.Play(isLooping, volume, position, 0);
if (!d["isPlaying"])
ai.Pause();
ai._LoadAdditionalState(d)
}
_OnMicrophoneStream(localMediaStream, tag) {
if (this._microphoneSource)
this._microphoneSource["disconnect"]();
this._microphoneTag = tag.toLowerCase();
this._microphoneSource = this._audioContext["createMediaStreamSource"](localMediaStream);
this._microphoneSource["connect"](this.GetDestinationForTag(this._microphoneTag))
}
_OnGetOutputStream() {
if (!this._destMediaStreamNode) {
this._destMediaStreamNode = this._audioContext["createMediaStreamDestination"]();
this._destinationNode["connect"](this._destMediaStreamNode)
}
return this._destMediaStreamNode["stream"]
}
static EqualsNoCase(a, b) {
if (a.length !== b.length)
return false;
if (a === b)
return true;
return a.toLowerCase() === b.toLowerCase()
}
static ToDegrees(x) {
return x * R_TO_D
}
static DbToLinearNoCap(x) {
return Math.pow(10, x / 20)
}
static DbToLinear(x) {
return Math.max(Math.min(self.AudioDOMHandler.DbToLinearNoCap(x), 1), 0)
}
static LinearToDbNoCap(x) {
return Math.log(x) / Math.log(10) * 20
}
static LinearToDb(x) {
return self.AudioDOMHandler.LinearToDbNoCap(Math.max(Math.min(x, 1), 0))
}
static e4(x, k) {
return 1 - Math.exp(-k * x)
}
}
;
self.RuntimeInterface.AddDOMHandlerClass(self.AudioDOMHandler)
}
;'use strict';
{
self.C3AudioBuffer = class C3AudioBuffer {
constructor(audioDomHandler, originalUrl, url, type, isMusic) {
this._audioDomHandler = audioDomHandler;
this._originalUrl = originalUrl;
this._url = url;
this._type = type;
this._isMusic = isMusic;
this._api = "";
this._loadState = "not-loaded";
this._loadPromise = null
}
Release() {
this._loadState = "not-loaded";
this._audioDomHandler = null;
this._loadPromise = null
}
static Create(audioDomHandler, originalUrl, url, type, isMusic) {
const needsSoftwareDecode = type === "audio/webm; codecs=opus" && !audioDomHandler.SupportsWebMOpus();
if (isMusic && needsSoftwareDecode)
audioDomHandler._SetHasAnySoftwareDecodedMusic();
if (!isMusic || audioDomHandler.IsPlayMusicAsSound() || needsSoftwareDecode)
return new self.C3WebAudioBuffer(audioDomHandler,originalUrl,url,type,isMusic,needsSoftwareDecode);
else
return new self.C3Html5AudioBuffer(audioDomHandler,originalUrl,url,type,isMusic)
}
CreateInstance(tag) {
if (this._api === "html5")
return new self.C3Html5AudioInstance(this._audioDomHandler,this,tag);
else
return new self.C3WebAudioInstance(this._audioDomHandler,this,tag)
}
_Load() {}
Load() {
if (!this._loadPromise)
this._loadPromise = this._Load();
return this._loadPromise
}
IsLoaded() {}
IsLoadedAndDecoded() {}
HasFailedToLoad() {
return this._loadState === "failed"
}
GetAudioContext() {
return this._audioDomHandler.GetAudioContext()
}
GetApi() {
return this._api
}
GetOriginalUrl() {
return this._originalUrl
}
GetUrl() {
return this._url
}
GetContentType() {
return this._type
}
IsMusic() {
return this._isMusic
}
GetDuration() {}
}
}
;'use strict';
{
self.C3Html5AudioBuffer = class C3Html5AudioBuffer extends self.C3AudioBuffer {
constructor(audioDomHandler, originalUrl, url, type, isMusic) {
super(audioDomHandler, originalUrl, url, type, isMusic);
this._api = "html5";
this._audioElem = new Audio;
this._audioElem.crossOrigin = "anonymous";
this._audioElem.autoplay = false;
this._audioElem.preload = "auto";
this._loadResolve = null;
this._loadReject = null;
this._reachedCanPlayThrough = false;
this._audioElem.addEventListener("canplaythrough", ()=>this._reachedCanPlayThrough = true);
this._outNode = this.GetAudioContext()["createGain"]();
this._mediaSourceNode = null;
this._audioElem.addEventListener("canplay", ()=>{
if (this._loadResolve) {
this._loadState = "loaded";
this._loadResolve();
this._loadResolve = null;
this._loadReject = null
}
if (this._mediaSourceNode || !this._audioElem)
return;
this._mediaSourceNode = this.GetAudioContext()["createMediaElementSource"](this._audioElem);
this._mediaSourceNode["connect"](this._outNode)
}
);
this.onended = null;
this._audioElem.addEventListener("ended", ()=>{
if (this.onended)
this.onended()
}
);
this._audioElem.addEventListener("error", e=>this._OnError(e))
}
Release() {
this._audioDomHandler.ReleaseInstancesForBuffer(this);
this._outNode["disconnect"]();
this._outNode = null;
this._mediaSourceNode["disconnect"]();
this._mediaSourceNode = null;
if (this._audioElem && !this._audioElem.paused)
this._audioElem.pause();
this.onended = null;
this._audioElem = null;
super.Release()
}
_Load() {
this._loadState = "loading";
return new Promise((resolve,reject)=>{
this._loadResolve = resolve;
this._loadReject = reject;
this._audioElem.src = this._url
}
)
}
_OnError(e) {
console.error(`[Construct 3] Audio '${this._url}' error: `, e);
if (this._loadReject) {
this._loadState = "failed";
this._loadReject(e);
this._loadResolve = null;
this._loadReject = null
}
}
IsLoaded() {
const ret = this._audioElem["readyState"] >= 4;
if (ret)
this._reachedCanPlayThrough = true;
return ret || this._reachedCanPlayThrough
}
IsLoadedAndDecoded() {
return this.IsLoaded()
}
GetAudioElement() {
return this._audioElem
}
GetOutputNode() {
return this._outNode
}
GetDuration() {
return this._audioElem["duration"]
}
}
}
;'use strict';
{
self.C3WebAudioBuffer = class C3WebAudioBuffer extends self.C3AudioBuffer {
constructor(audioDomHandler, originalUrl, url, type, isMusic, needsSoftwareDecode) {
super(audioDomHandler, originalUrl, url, type, isMusic);
this._api = "webaudio";
this._audioData = null;
this._audioBuffer = null;
this._needsSoftwareDecode = !!needsSoftwareDecode
}
Release() {
this._audioDomHandler.ReleaseInstancesForBuffer(this);
this._audioData = null;
this._audioBuffer = null;
super.Release()
}
async _Fetch() {
if (this._audioData)
return this._audioData;
const iRuntime = this._audioDomHandler.GetRuntimeInterface();
if (iRuntime.GetExportType() === "cordova" && iRuntime.IsRelativeURL(this._url) && location.protocol === "file:")
this._audioData = await iRuntime.CordovaFetchLocalFileAsArrayBuffer(this._url);
else {
const response = await fetch(this._url);
if (!response.ok)
throw new Error(`error fetching audio data: ${response.status} ${response.statusText}`);
this._audioData = await response.arrayBuffer()
}
}
async _Decode() {
if (this._audioBuffer)
return this._audioBuffer;
this._audioBuffer = await this._audioDomHandler.DecodeAudioData(this._audioData, this._needsSoftwareDecode);
this._audioData = null
}
async _Load() {
try {
this._loadState = "loading";
await this._Fetch();
await this._Decode();
this._loadState = "loaded"
} catch (err) {
this._loadState = "failed";
console.error(`[Construct 3] Failed to load audio '${this._url}': `, err)
}
}
IsLoaded() {
return !!(this._audioData || this._audioBuffer)
}
IsLoadedAndDecoded() {
return !!this._audioBuffer
}
GetAudioBuffer() {
return this._audioBuffer
}
GetDuration() {
return this._audioBuffer ? this._audioBuffer["duration"] : 0
}
}
}
;'use strict';
{
let nextAiId = 0;
self.C3AudioInstance = class C3AudioInstance {
constructor(audioDomHandler, buffer, tag) {
this._audioDomHandler = audioDomHandler;
this._buffer = buffer;
this._tag = tag;
this._aiId = nextAiId++;
this._gainNode = this.GetAudioContext()["createGain"]();
this._gainNode["connect"](this.GetDestinationNode());
this._pannerNode = null;
this._isPannerEnabled = false;
this._isStopped = true;
this._isPaused = false;
this._resumeMe = false;
this._isLooping = false;
this._volume = 1;
this._isMuted = false;
this._playbackRate = 1;
const timeScaleMode = this._audioDomHandler.GetTimeScaleMode();
this._isTimescaled = timeScaleMode === 1 && !this.IsMusic() || timeScaleMode === 2;
this._instUid = -1;
this._fadeEndTime = -1;
this._stopOnFadeEnd = false
}
Release() {
this._audioDomHandler = null;
this._buffer = null;
if (this._pannerNode) {
this._pannerNode["disconnect"]();
this._pannerNode = null
}
this._gainNode["disconnect"]();
this._gainNode = null
}
GetAudioContext() {
return this._audioDomHandler.GetAudioContext()
}
GetDestinationNode() {
return this._audioDomHandler.GetDestinationForTag(this._tag)
}
GetMasterVolume() {
return this._audioDomHandler.GetMasterVolume()
}
GetCurrentTime() {
if (this._isTimescaled)
return this._audioDomHandler.GetGameTime();
else
return performance.now() / 1E3
}
GetOriginalUrl() {
return this._buffer.GetOriginalUrl()
}
GetUrl() {
return this._buffer.GetUrl()
}
GetContentType() {
return this._buffer.GetContentType()
}
GetBuffer() {
return this._buffer
}
IsMusic() {
return this._buffer.IsMusic()
}
SetTag(tag) {
this._tag = tag
}
GetTag() {
return this._tag
}
GetAiId() {
return this._aiId
}
HasEnded() {}
CanBeRecycled() {}
IsPlaying() {
return !this._isStopped && !this._isPaused && !this.HasEnded()
}
IsActive() {
return !this._isStopped && !this.HasEnded()
}
GetPlaybackTime(applyPlaybackRate) {}
GetDuration(applyPlaybackRate) {
let ret = this._buffer.GetDuration();
if (applyPlaybackRate)
ret /= this._playbackRate || .001;
return ret
}
Play(isLooping, vol, seekPos, scheduledTime) {}
Stop() {}
Pause() {}
IsPaused() {
return this._isPaused
}
Resume() {}
SetVolume(v) {
this._volume = v;
this._gainNode["gain"]["cancelScheduledValues"](0);
this._fadeEndTime = -1;
this._gainNode["gain"]["value"] = this.GetOverallVolume()
}
FadeVolume(vol, duration, stopOnEnd) {
if (this.IsMuted())
return;
vol *= this.GetMasterVolume();
const gainParam = this._gainNode["gain"];
gainParam["cancelScheduledValues"](0);
const currentTime = this._audioDomHandler.GetAudioCurrentTime();
const endTime = currentTime + duration;
gainParam["setValueAtTime"](gainParam["value"], currentTime);
gainParam["linearRampToValueAtTime"](vol, endTime);
this._volume = vol;
this._fadeEndTime = endTime;
this._stopOnFadeEnd = stopOnEnd
}
_UpdateVolume() {
this.SetVolume(this._volume)
}
Tick(currentTime) {
if (this._fadeEndTime !== -1 && currentTime >= this._fadeEndTime) {
this._fadeEndTime = -1;
if (this._stopOnFadeEnd)
this.Stop();
this._audioDomHandler.PostTrigger("fade-ended", this._tag, this._aiId)
}
}
GetOverallVolume() {
const ret = this._volume * this.GetMasterVolume();
return isFinite(ret) ? ret : 0
}
SetMuted(m) {
m = !!m;
if (this._isMuted === m)
return;
this._isMuted = m;
this._UpdateMuted()
}
IsMuted() {
return this._isMuted
}
IsSilent() {
return this._audioDomHandler.IsSilent()
}
_UpdateMuted() {}
SetLooping(l) {}
IsLooping() {
return this._isLooping
}
SetPlaybackRate(r) {
if (this._playbackRate === r)
return;
this._playbackRate = r;
this._UpdatePlaybackRate()
}
_UpdatePlaybackRate() {}
GetPlaybackRate() {
return this._playbackRate
}
Seek(pos) {}
SetSuspended(s) {}
SetPannerEnabled(e) {
e = !!e;
if (this._isPannerEnabled === e)
return;
this._isPannerEnabled = e;
if (this._isPannerEnabled) {
if (!this._pannerNode) {
this._pannerNode = this.GetAudioContext()["createPanner"]();
this._pannerNode["panningModel"] = this._audioDomHandler.GetPanningModel();
this._pannerNode["distanceModel"] = this._audioDomHandler.GetDistanceModel();
this._pannerNode["refDistance"] = this._audioDomHandler.GetReferenceDistance();
this._pannerNode["maxDistance"] = this._audioDomHandler.GetMaxDistance();
this._pannerNode["rolloffFactor"] = this._audioDomHandler.GetRolloffFactor()
}
this._gainNode["disconnect"]();
this._gainNode["connect"](this._pannerNode);
this._pannerNode["connect"](this.GetDestinationNode())
} else {
this._pannerNode["disconnect"]();
this._gainNode["disconnect"]();
this._gainNode["connect"](this.GetDestinationNode())
}
}
SetPan(x, y, angle, innerAngle, outerAngle, outerGain) {
if (!this._isPannerEnabled)
return;
this.SetPanXYA(x, y, angle);
const toDegrees = self.AudioDOMHandler.ToDegrees;
this._pannerNode["coneInnerAngle"] = toDegrees(innerAngle);
this._pannerNode["coneOuterAngle"] = toDegrees(outerAngle);
this._pannerNode["coneOuterGain"] = outerGain
}
SetPanXYA(x, y, angle) {
if (!this._isPannerEnabled)
return;
this._pannerNode["setPosition"](x, y, 0);
this._pannerNode["setOrientation"](Math.cos(angle), Math.sin(angle), 0)
}
SetUID(uid) {
this._instUid = uid
}
GetUID() {
return this._instUid
}
GetResumePosition() {}
Reconnect(toNode) {
const outNode = this._pannerNode || this._gainNode;
outNode["disconnect"]();
outNode["connect"](toNode)
}
GetState() {
return {
"aiid": this.GetAiId(),
"tag": this._tag,
"duration": this.GetDuration(),
"volume": this._volume,
"isPlaying": this.IsPlaying(),
"playbackTime": this.GetPlaybackTime(),
"playbackRate": this.GetPlaybackRate(),
"uid": this._instUid,
"bufferOriginalUrl": this.GetOriginalUrl(),
"bufferUrl": "",
"bufferType": this.GetContentType(),
"isMusic": this.IsMusic(),
"isLooping": this.IsLooping(),
"isMuted": this.IsMuted(),
"resumePosition": this.GetResumePosition(),
"pan": this.GetPanState()
}
}
_LoadAdditionalState(d) {
this.SetPlaybackRate(d["playbackRate"]);
this.SetMuted(d["isMuted"])
}
GetPanState() {
if (!this._pannerNode)
return null;
const pn = this._pannerNode;
return {
"pos": [pn["positionX"]["value"], pn["positionY"]["value"], pn["positionZ"]["value"]],
"orient": [pn["orientationX"]["value"], pn["orientationY"]["value"], pn["orientationZ"]["value"]],
"cia": pn["coneInnerAngle"],
"coa": pn["coneOuterAngle"],
"cog": pn["coneOuterGain"],
"uid": this._instUid
}
}
LoadPanState(d) {
if (!d) {
this.SetPannerEnabled(false);
return
}
this.SetPannerEnabled(true);
const pn = this._pannerNode;
pn["setPosition"](...pn["pos"]);
pn["setOrientation"](...pn["orient"]);
pn["coneInnerAngle"] = pn["cia"];
pn["coneOuterAngle"] = pn["coa"];
pn["coneOuterGain"] = pn["cog"];
this._instUid = pn["uid"]
}
}
}
;'use strict';
{
self.C3Html5AudioInstance = class C3Html5AudioInstance extends self.C3AudioInstance {
constructor(audioDomHandler, buffer, tag) {
super(audioDomHandler, buffer, tag);
this._buffer.GetOutputNode()["connect"](this._gainNode);
this._buffer.onended = ()=>this._OnEnded()
}
Release() {
this.Stop();
this._buffer.GetOutputNode()["disconnect"]();
super.Release()
}
GetAudioElement() {
return this._buffer.GetAudioElement()
}
_OnEnded() {
this._isStopped = true;
this._instUid = -1;
this._audioDomHandler.PostTrigger("ended", this._tag, this._aiId)
}
HasEnded() {
return this.GetAudioElement()["ended"]
}
CanBeRecycled() {
if (this._isStopped)
return true;
return this.HasEnded()
}
GetPlaybackTime(applyPlaybackRate) {
let ret = this.GetAudioElement()["currentTime"];
if (applyPlaybackRate)
ret *= this._playbackRate;
if (!this._isLooping)
ret = Math.min(ret, this.GetDuration());
return ret
}
Play(isLooping, vol, seekPos, scheduledTime) {
const audioElem = this.GetAudioElement();
if (audioElem.playbackRate !== 1)
audioElem.playbackRate = 1;
if (audioElem.loop !== isLooping)
audioElem.loop = isLooping;
this.SetVolume(vol);
if (audioElem.muted)
audioElem.muted = false;
if (audioElem.currentTime !== seekPos)
try {
audioElem.currentTime = seekPos
} catch (err) {
console.warn(`[Construct 3] Exception seeking audio '${this._buffer.GetUrl()}' to position '${seekPos}': `, err)
}
this._audioDomHandler.TryPlayMedia(audioElem);
this._isStopped = false;
this._isPaused = false;
this._isLooping = isLooping;
this._playbackRate = 1
}
Stop() {
const audioElem = this.GetAudioElement();
if (!audioElem.paused)
audioElem.pause();
this._audioDomHandler.RemovePendingPlay(audioElem);
this._isStopped = true;
this._isPaused = false;
this._instUid = -1
}
Pause() {
if (this._isPaused || this._isStopped || this.HasEnded())
return;
const audioElem = this.GetAudioElement();
if (!audioElem.paused)
audioElem.pause();
this._audioDomHandler.RemovePendingPlay(audioElem);
this._isPaused = true
}
Resume() {
if (!this._isPaused || this._isStopped || this.HasEnded())
return;
this._audioDomHandler.TryPlayMedia(this.GetAudioElement());
this._isPaused = false
}
_UpdateMuted() {
this.GetAudioElement().muted = this._isMuted || this.IsSilent()
}
SetLooping(l) {
l = !!l;
if (this._isLooping === l)
return;
this._isLooping = l;
this.GetAudioElement().loop = l
}
_UpdatePlaybackRate() {
let r = this._playbackRate;
if (this._isTimescaled)
r *= this._audioDomHandler.GetTimeScale();
try {
this.GetAudioElement()["playbackRate"] = r
} catch (err) {
console.warn(`[Construct 3] Unable to set playback rate '${r}':`, err)
}
}
Seek(pos) {
if (this._isStopped || this.HasEnded())
return;
try {
this.GetAudioElement()["currentTime"] = pos
} catch (err) {
console.warn(`[Construct 3] Error seeking audio to '${pos}': `, err)
}
}
GetResumePosition() {
return this.GetPlaybackTime()
}
SetSuspended(s) {
if (s)
if (this.IsPlaying()) {
this.GetAudioElement()["pause"]();
this._resumeMe = true
} else
this._resumeMe = false;
else if (this._resumeMe) {
this._audioDomHandler.TryPlayMedia(this.GetAudioElement());
this._resumeMe = false
}
}
}
}
;'use strict';
{
self.C3WebAudioInstance = class C3WebAudioInstance extends self.C3AudioInstance {
constructor(audioDomHandler, buffer, tag) {
super(audioDomHandler, buffer, tag);
this._bufferSource = null;
this._onended_handler = e=>this._OnEnded(e);
this._hasPlaybackEnded = true;
this._activeSource = null;
this._startTime = 0;
this._resumePosition = 0;
this._muteVol = 1
}
Release() {
this.Stop();
this._ReleaseBufferSource();
this._onended_handler = null;
super.Release()
}
_ReleaseBufferSource() {
if (this._bufferSource)
this._bufferSource["disconnect"]();
this._bufferSource = null;
this._activeSource = null
}
_OnEnded(e) {
if (this._isPaused || this._resumeMe)
return;
if (e.target !== this._activeSource)
return;
this._hasPlaybackEnded = true;
this._isStopped = true;
this._instUid = -1;
this._ReleaseBufferSource();
this._audioDomHandler.PostTrigger("ended", this._tag, this._aiId)
}
HasEnded() {
if (!this._isStopped && this._bufferSource && this._bufferSource["loop"])
return false;
if (this._isPaused)
return false;
return this._hasPlaybackEnded
}
CanBeRecycled() {
if (!this._bufferSource || this._isStopped)
return true;
return this.HasEnded()
}
GetPlaybackTime(applyPlaybackRate) {
let ret = 0;
if (this._isPaused)
ret = this._resumePosition;
else
ret = this.GetCurrentTime() - this._startTime;
if (applyPlaybackRate)
ret *= this._playbackRate;
if (!this._isLooping)
ret = Math.min(ret, this.GetDuration());
return ret
}
Play(isLooping, vol, seekPos, scheduledTime) {
this._muteVol = 1;
this.SetVolume(vol);
this._ReleaseBufferSource();
this._bufferSource = this.GetAudioContext()["createBufferSource"]();
this._bufferSource["buffer"] = this._buffer.GetAudioBuffer();
this._bufferSource["connect"](this._gainNode);
this._activeSource = this._bufferSource;
this._bufferSource["onended"] = this._onended_handler;
this._bufferSource["loop"] = isLooping;
this._bufferSource["start"](scheduledTime, seekPos);
this._hasPlaybackEnded = false;
this._isStopped = false;
this._isPaused = false;
this._isLooping = isLooping;
this._playbackRate = 1;
this._startTime = this.GetCurrentTime() - seekPos
}
Stop() {
if (this._bufferSource)
try {
this._bufferSource["stop"](0)
} catch (err) {}
this._isStopped = true;
this._isPaused = false;
this._instUid = -1
}
Pause() {
if (this._isPaused || this._isStopped || this.HasEnded())
return;
this._resumePosition = this.GetPlaybackTime(true);
if (this._isLooping)
this._resumePosition %= this.GetDuration();
this._isPaused = true;
this._bufferSource["stop"](0)
}
Resume() {
if (!this._isPaused || this._isStopped || this.HasEnded())
return;
this._ReleaseBufferSource();
this._bufferSource = this.GetAudioContext()["createBufferSource"]();
this._bufferSource["buffer"] = this._buffer.GetAudioBuffer();
this._bufferSource["connect"](this._gainNode);
this._activeSource = this._bufferSource;
this._bufferSource["onended"] = this._onended_handler;
this._bufferSource["loop"] = this._isLooping;
this._UpdateVolume();
this._UpdatePlaybackRate();
this._startTime = this.GetCurrentTime() - this._resumePosition / (this._playbackRate || .001);
this._bufferSource["start"](0, this._resumePosition);
this._isPaused = false
}
GetOverallVolume() {
return super.GetOverallVolume() * this._muteVol
}
_UpdateMuted() {
this._muteVol = this._isMuted || this.IsSilent() ? 0 : 1;
this._UpdateVolume()
}
SetLooping(l) {
l = !!l;
if (this._isLooping === l)
return;
this._isLooping = l;
if (this._bufferSource)
this._bufferSource["loop"] = l
}
_UpdatePlaybackRate() {
let r = this._playbackRate;
if (this._isTimescaled)
r *= this._audioDomHandler.GetTimeScale();
if (this._bufferSource)
this._bufferSource["playbackRate"]["value"] = r
}
Seek(pos) {
if (this._isStopped || this.HasEnded())
return;
if (this._isPaused)
this._resumePosition = pos;
else {
this.Pause();
this._resumePosition = pos;
this.Resume()
}
}
GetResumePosition() {
return this._resumePosition
}
SetSuspended(s) {
if (s)
if (this.IsPlaying()) {
this._resumeMe = true;
this._resumePosition = this.GetPlaybackTime(true);
if (this._isLooping)
this._resumePosition %= this.GetDuration();
this._bufferSource["stop"](0)
} else
this._resumeMe = false;
else if (this._resumeMe) {
this._ReleaseBufferSource();
this._bufferSource = this.GetAudioContext()["createBufferSource"]();
this._bufferSource["buffer"] = this._buffer.GetAudioBuffer();
this._bufferSource["connect"](this._gainNode);
this._activeSource = this._bufferSource;
this._bufferSource["onended"] = this._onended_handler;
this._bufferSource["loop"] = this._isLooping;
this._UpdateVolume();
this._UpdatePlaybackRate();
this._startTime = this.GetCurrentTime() - this._resumePosition / (this._playbackRate || .001);
this._bufferSource["start"](0, this._resumePosition);
this._resumeMe = false
}
}
_LoadAdditionalState(d) {
super._LoadAdditionalState(d);
this._resumePosition = d["resumePosition"]
}
}
}
;'use strict';
{
class AudioFXBase {
constructor(audioDomHandler) {
this._audioDomHandler = audioDomHandler;
this._audioContext = audioDomHandler.GetAudioContext();
this._index = -1;
this._tag = "";
this._type = "";
this._params = null
}
Release() {
this._audioContext = null
}
_SetIndex(i) {
this._index = i
}
GetIndex() {
return this._index
}
_SetTag(t) {
this._tag = t
}
GetTag() {
return this._tag
}
CreateGain() {
return this._audioContext["createGain"]()
}
GetInputNode() {}
ConnectTo(node) {}
SetAudioParam(ap, value, ramp, time) {
ap["cancelScheduledValues"](0);
if (time === 0) {
ap["value"] = value;
return
}
const curTime = this._audioContext["currentTime"];
time += curTime;
switch (ramp) {
case 0:
ap["setValueAtTime"](value, time);
break;
case 1:
ap["setValueAtTime"](ap["value"], curTime);
ap["linearRampToValueAtTime"](value, time);
break;
case 2:
ap["setValueAtTime"](ap["value"], curTime);
ap["exponentialRampToValueAtTime"](value, time);
break
}
}
GetState() {
return {
"type": this._type,
"tag": this._tag,
"params": this._params
}
}
}
self.C3AudioFilterFX = class C3AudioFilterFX extends AudioFXBase {
constructor(audioDomHandler, type, freq, detune, q, gain, mix) {
super(audioDomHandler);
this._type = "filter";
this._params = [type, freq, detune, q, gain, mix];
this._inputNode = this.CreateGain();
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix;
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix;
this._filterNode = this._audioContext["createBiquadFilter"]();
this._filterNode["type"] = type;
this._filterNode["frequency"]["value"] = freq;
this._filterNode["detune"]["value"] = detune;
this._filterNode["Q"]["value"] = q;
this._filterNode["gain"]["vlaue"] = gain;
this._inputNode["connect"](this._filterNode);
this._inputNode["connect"](this._dryNode);
this._filterNode["connect"](this._wetNode)
}
Release() {
this._inputNode["disconnect"]();
this._filterNode["disconnect"]();
this._wetNode["disconnect"]();
this._dryNode["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[5] = value;
this.SetAudioParam(this._wetNode["gain"], value, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value, ramp, time);
break;
case 1:
this._params[1] = value;
this.SetAudioParam(this._filterNode["frequency"], value, ramp, time);
break;
case 2:
this._params[2] = value;
this.SetAudioParam(this._filterNode["detune"], value, ramp, time);
break;
case 3:
this._params[3] = value;
this.SetAudioParam(this._filterNode["Q"], value, ramp, time);
break;
case 4:
this._params[4] = value;
this.SetAudioParam(this._filterNode["gain"], value, ramp, time);
break
}
}
}
;
self.C3AudioDelayFX = class C3AudioDelayFX extends AudioFXBase {
constructor(audioDomHandler, delayTime, delayGain, mix) {
super(audioDomHandler);
this._type = "delay";
this._params = [delayTime, delayGain, mix];
this._inputNode = this.CreateGain();
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix;
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix;
this._mainNode = this.CreateGain();
this._delayNode = this._audioContext["createDelay"](delayTime);
this._delayNode["delayTime"]["value"] = delayTime;
this._delayGainNode = this.CreateGain();
this._delayGainNode["gain"]["value"] = delayGain;
this._inputNode["connect"](this._mainNode);
this._inputNode["connect"](this._dryNode);
this._mainNode["connect"](this._wetNode);
this._mainNode["connect"](this._delayNode);
this._delayNode["connect"](this._delayGainNode);
this._delayGainNode["connect"](this._mainNode)
}
Release() {
this._inputNode["disconnect"]();
this._wetNode["disconnect"]();
this._dryNode["disconnect"]();
this._mainNode["disconnect"]();
this._delayNode["disconnect"]();
this._delayGainNode["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
const DbToLinear = self.AudioDOMHandler.DbToLinear;
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[2] = value;
this.SetAudioParam(this._wetNode["gain"], value, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value, ramp, time);
break;
case 4:
this._params[1] = DbToLinear(value);
this.SetAudioParam(this._delayGainNode["gain"], DbToLinear(value), ramp, time);
break;
case 5:
this._params[0] = value;
this.SetAudioParam(this._delayNode["delayTime"], value, ramp, time);
break
}
}
}
;
self.C3AudioConvolveFX = class C3AudioConvolveFX extends AudioFXBase {
constructor(audioDomHandler, buffer, normalize, mix) {
super(audioDomHandler);
this._type = "convolution";
this._params = [normalize, mix];
this._bufferOriginalUrl = "";
this._bufferUrl = "";
this._bufferType = "";
this._inputNode = this.CreateGain();
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix;
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix;
this._convolveNode = this._audioContext["createConvolver"]();
this._convolveNode["normalize"] = normalize;
this._convolveNode["buffer"] = buffer;
this._inputNode["connect"](this._convolveNode);
this._inputNode["connect"](this._dryNode);
this._convolveNode["connect"](this._wetNode)
}
Release() {
this._inputNode["disconnect"]();
this._convolveNode["disconnect"]();
this._wetNode["disconnect"]();
this._dryNode["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[1] = value;
this.SetAudioParam(this._wetNode["gain"], value, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value, ramp, time);
break
}
}
_SetBufferInfo(bufferOriginalUrl, bufferUrl, bufferType) {
this._bufferOriginalUrl = bufferOriginalUrl;
this._bufferUrl = bufferUrl;
this._bufferType = bufferType
}
GetState() {
const ret = super.GetState();
ret["bufferOriginalUrl"] = this._bufferOriginalUrl;
ret["bufferUrl"] = "";
ret["bufferType"] = this._bufferType;
return ret
}
}
;
self.C3AudioFlangerFX = class C3AudioFlangerFX extends AudioFXBase {
constructor(audioDomHandler, delay, modulation, freq, feedback, mix) {
super(audioDomHandler);
this._type = "flanger";
this._params = [delay, modulation, freq, feedback, mix];
this._inputNode = this.CreateGain();
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix / 2;
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix / 2;
this._feedbackNode = this.CreateGain();
this._feedbackNode["gain"]["value"] = feedback;
this._delayNode = this._audioContext["createDelay"](delay + modulation);
this._delayNode["delayTime"]["value"] = delay;
this._oscNode = this._audioContext["createOscillator"]();
this._oscNode["frequency"]["value"] = freq;
this._oscGainNode = this.CreateGain();
this._oscGainNode["gain"]["value"] = modulation;
this._inputNode["connect"](this._delayNode);
this._inputNode["connect"](this._dryNode);
this._delayNode["connect"](this._wetNode);
this._delayNode["connect"](this._feedbackNode);
this._feedbackNode["connect"](this._delayNode);
this._oscNode["connect"](this._oscGainNode);
this._oscGainNode["connect"](this._delayNode["delayTime"]);
this._oscNode["start"](0)
}
Release() {
this._oscNode["stop"](0);
this._inputNode["disconnect"]();
this._delayNode["disconnect"]();
this._oscNode["disconnect"]();
this._oscGainNode["disconnect"]();
this._dryNode["disconnect"]();
this._wetNode["disconnect"]();
this._feedbackNode["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[4] = value;
this.SetAudioParam(this._wetNode["gain"], value / 2, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value / 2, ramp, time);
break;
case 6:
this._params[1] = value / 1E3;
this.SetAudioParam(this._oscGainNode["gain"], value / 1E3, ramp, time);
break;
case 7:
this._params[2] = value;
this.SetAudioParam(this._oscNode["frequency"], value, ramp, time);
break;
case 8:
this._params[3] = value / 100;
this.SetAudioParam(this._feedbackNode["gain"], value / 100, ramp, time);
break
}
}
}
;
self.C3AudioPhaserFX = class C3AudioPhaserFX extends AudioFXBase {
constructor(audioDomHandler, freq, detune, q, modulation, modfreq, mix) {
super(audioDomHandler);
this._type = "phaser";
this._params = [freq, detune, q, modulation, modfreq, mix];
this._inputNode = this.CreateGain();
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix / 2;
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix / 2;
this._filterNode = this._audioContext["createBiquadFilter"]();
this._filterNode["type"] = "allpass";
this._filterNode["frequency"]["value"] = freq;
this._filterNode["detune"]["value"] = detune;
this._filterNode["Q"]["value"] = q;
this._oscNode = this._audioContext["createOscillator"]();
this._oscNode["frequency"]["value"] = modfreq;
this._oscGainNode = this.CreateGain();
this._oscGainNode["gain"]["value"] = modulation;
this._inputNode["connect"](this._filterNode);
this._inputNode["connect"](this._dryNode);
this._filterNode["connect"](this._wetNode);
this._oscNode["connect"](this._oscGainNode);
this._oscGainNode["connect"](this._filterNode["frequency"]);
this._oscNode["start"](0)
}
Release() {
this._oscNode["stop"](0);
this._inputNode["disconnect"]();
this._filterNode["disconnect"]();
this._oscNode["disconnect"]();
this._oscGainNode["disconnect"]();
this._dryNode["disconnect"]();
this._wetNode["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[5] = value;
this.SetAudioParam(this._wetNode["gain"], value / 2, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value / 2, ramp, time);
break;
case 1:
this._params[0] = value;
this.SetAudioParam(this._filterNode["frequency"], value, ramp, time);
break;
case 2:
this._params[1] = value;
this.SetAudioParam(this._filterNode["detune"], value, ramp, time);
break;
case 3:
this._params[2] = value;
this.SetAudioParam(this._filterNode["Q"], value, ramp, time);
break;
case 6:
this._params[3] = value;
this.SetAudioParam(this._oscGainNode["gain"], value, ramp, time);
break;
case 7:
this._params[4] = value;
this.SetAudioParam(this._oscNode["frequency"], value, ramp, time);
break
}
}
}
;
self.C3AudioGainFX = class C3AudioGainFX extends AudioFXBase {
constructor(audioDomHandler, g) {
super(audioDomHandler);
this._type = "gain";
this._params = [g];
this._node = this.CreateGain();
this._node["gain"]["value"] = g
}
Release() {
this._node["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._node["disconnect"]();
this._node["connect"](node)
}
GetInputNode() {
return this._node
}
SetParam(param, value, ramp, time) {
const DbToLinear = self.AudioDOMHandler.DbToLinear;
switch (param) {
case 4:
this._params[0] = DbToLinear(value);
this.SetAudioParam(this._node["gain"], DbToLinear(value), ramp, time);
break
}
}
}
;
self.C3AudioTremoloFX = class C3AudioTremoloFX extends AudioFXBase {
constructor(audioDomHandler, freq, mix) {
super(audioDomHandler);
this._type = "tremolo";
this._params = [freq, mix];
this._node = this.CreateGain();
this._node["gain"]["value"] = 1 - mix / 2;
this._oscNode = this._audioContext["createOscillator"]();
this._oscNode["frequency"]["value"] = freq;
this._oscGainNode = this.CreateGain();
this._oscGainNode["gain"]["value"] = mix / 2;
this._oscNode["connect"](this._oscGainNode);
this._oscGainNode["connect"](this._node["gain"]);
this._oscNode["start"](0)
}
Release() {
this._oscNode["stop"](0);
this._oscNode["disconnect"]();
this._oscGainNode["disconnect"]();
this._node["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._node["disconnect"]();
this._node["connect"](node)
}
GetInputNode() {
return this._node
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[1] = value;
this.SetAudioParam(this._node["gain"]["value"], 1 - value / 2, ramp, time);
this.SetAudioParam(this._oscGainNode["gain"]["value"], value / 2, ramp, time);
break;
case 7:
this._params[0] = value;
this.SetAudioParam(this._oscNode["frequency"], value, ramp, time);
break
}
}
}
;
self.C3AudioRingModFX = class C3AudioRingModFX extends AudioFXBase {
constructor(audioDomHandler, freq, mix) {
super(audioDomHandler);
this._type = "ringmod";
this._params = [freq, mix];
this._inputNode = this.CreateGain();
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix;
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix;
this._ringNode = this.CreateGain();
this._ringNode["gain"]["value"] = 0;
this._oscNode = this._audioContext["createOscillator"]();
this._oscNode["frequency"]["value"] = freq;
this._oscNode["connect"](this._ringNode["gain"]);
this._oscNode["start"](0);
this._inputNode["connect"](this._ringNode);
this._inputNode["connect"](this._dryNode);
this._ringNode["connect"](this._wetNode)
}
Release() {
this._oscNode["stop"](0);
this._oscNode["disconnect"]();
this._ringNode["disconnect"]();
this._inputNode["disconnect"]();
this._wetNode["disconnect"]();
this._dryNode["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[1] = value;
this.SetAudioParam(this._wetNode["gain"], value, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value, ramp, time);
break;
case 7:
this._params[0] = value;
this.SetAudioParam(this._oscNode["frequency"], value, ramp, time);
break
}
}
}
;
self.C3AudioDistortionFX = class C3AudioDistortionFX extends AudioFXBase {
constructor(audioDomHandler, threshold, headroom, drive, makeupgain, mix) {
super(audioDomHandler);
this._type = "distortion";
this._params = [threshold, headroom, drive, makeupgain, mix];
this._inputNode = this.CreateGain();
this._preGain = this.CreateGain();
this._postGain = this.CreateGain();
this._SetDrive(drive, makeupgain);
this._wetNode = this.CreateGain();
this._wetNode["gain"]["value"] = mix;
this._dryNode = this.CreateGain();
this._dryNode["gain"]["value"] = 1 - mix;
this._waveShaper = this._audioContext["createWaveShaper"]();
this._curve = new Float32Array(65536);
this._GenerateColortouchCurve(threshold, headroom);
this._waveShaper.curve = this._curve;
this._inputNode["connect"](this._preGain);
this._inputNode["connect"](this._dryNode);
this._preGain["connect"](this._waveShaper);
this._waveShaper["connect"](this._postGain);
this._postGain["connect"](this._wetNode)
}
Release() {
this._inputNode["disconnect"]();
this._preGain["disconnect"]();
this._waveShaper["disconnect"]();
this._postGain["disconnect"]();
this._wetNode["disconnect"]();
this._dryNode["disconnect"]();
super.Release()
}
_SetDrive(drive, makeupgain) {
if (drive < .01)
drive = .01;
this._preGain["gain"]["value"] = drive;
this._postGain["gain"]["value"] = Math.pow(1 / drive, .6) * makeupgain
}
_GenerateColortouchCurve(threshold, headroom) {
const n = 65536;
const n2 = n / 2;
for (let i = 0; i < n2; ++i) {
let x = i / n2;
x = this._Shape(x, threshold, headroom);
this._curve[n2 + i] = x;
this._curve[n2 - i - 1] = -x
}
}
_Shape(x, threshold, headroom) {
const maximum = 1.05 * headroom * threshold;
const kk = maximum - threshold;
const sign = x < 0 ? -1 : +1;
const absx = x < 0 ? -x : x;
let shapedInput = absx < threshold ? absx : threshold + kk * self.AudioDOMHandler.e4(absx - threshold, 1 / kk);
shapedInput *= sign;
return shapedInput
}
ConnectTo(node) {
this._wetNode["disconnect"]();
this._wetNode["connect"](node);
this._dryNode["disconnect"]();
this._dryNode["connect"](node)
}
GetInputNode() {
return this._inputNode
}
SetParam(param, value, ramp, time) {
switch (param) {
case 0:
value = Math.max(Math.min(value / 100, 1), 0);
this._params[4] = value;
this.SetAudioParam(this._wetNode["gain"], value, ramp, time);
this.SetAudioParam(this._dryNode["gain"], 1 - value, ramp, time);
break
}
}
}
;
self.C3AudioCompressorFX = class C3AudioCompressorFX extends AudioFXBase {
constructor(audioDomHandler, threshold, knee, ratio, attack, release) {
super(audioDomHandler);
this._type = "compressor";
this._params = [threshold, knee, ratio, attack, release];
this._node = this._audioContext["createDynamicsCompressor"]();
this._node["threshold"]["value"] = threshold;
this._node["knee"]["value"] = knee;
this._node["ratio"]["value"] = ratio;
this._node["attack"]["value"] = attack;
this._node["release"]["value"] = release
}
Release() {
this._node["disconnect"]();
super.Release()
}
ConnectTo(node) {
this._node["disconnect"]();
this._node["connect"](node)
}
GetInputNode() {
return this._node
}
SetParam(param, value, ramp, time) {}
}
;
self.C3AudioAnalyserFX = class C3AudioAnalyserFX extends AudioFXBase {
constructor(audioDomHandler, fftSize, smoothing) {
super(audioDomHandler);
this._type = "analyser";
this._params = [fftSize, smoothing];
this._node = this._audioContext["createAnalyser"]();
this._node["fftSize"] = fftSize;
this._node["smoothingTimeConstant"] = smoothing;
this._freqBins = new Float32Array(this._node["frequencyBinCount"]);
this._signal = new Uint8Array(fftSize);
this._peak = 0;
this._rms = 0;
this._audioDomHandler._AddAnalyser(this)
}
Release() {
this._audioDomHandler._RemoveAnalyser(this);
this._node["disconnect"]();
super.Release()
}
Tick() {
this._node["getFloatFrequencyData"](this._freqBins);
this._node["getByteTimeDomainData"](this._signal);
const fftSize = this._node["fftSize"];
this._peak = 0;
let rmsSquaredSum = 0;
for (let i = 0; i < fftSize; ++i) {
let s = (this._signal[i] - 128) / 128;
if (s < 0)
s = -s;
if (this._peak < s)
this._peak = s;
rmsSquaredSum += s * s
}
const LinearToDb = self.AudioDOMHandler.LinearToDb;
this._peak = LinearToDb(this._peak);
this._rms = LinearToDb(Math.sqrt(rmsSquaredSum / fftSize))
}
ConnectTo(node) {
this._node["disconnect"]();
this._node["connect"](node)
}
GetInputNode() {
return this._node
}
SetParam(param, value, ramp, time) {}
GetData() {
return {
"tag": this.GetTag(),
"index": this.GetIndex(),
"peak": this._peak,
"rms": this._rms,
"binCount": this._node["frequencyBinCount"],
"freqBins": this._freqBins
}
}
}
}
;'use strict';
{
const DOM_COMPONENT_ID = "text-input";
function StopPropagation(e) {
e.stopPropagation()
}
function StopKeyPropagation(e) {
if (e.which !== 13 && e.which !== 27)
e.stopPropagation()
}
const HANDLER_CLASS = class TextInputDOMHandler extends self.DOMElementHandler {
constructor(iRuntime) {
super(iRuntime, DOM_COMPONENT_ID);
this.AddDOMElementMessageHandler("scroll-to-bottom", elem=>this._OnScrollToBottom(elem))
}
CreateElement(elementId, e) {
let elem;
const type = e["type"];
if (type === "textarea") {
elem = document.createElement("textarea");
elem.style.resize = "none"
} else {
elem = document.createElement("input");
elem.type = type
}
elem.style.position = "absolute";
elem.autocomplete = "off";
elem.addEventListener("touchstart", StopPropagation);
elem.addEventListener("touchmove", StopPropagation);
elem.addEventListener("touchend", StopPropagation);
elem.addEventListener("mousedown", StopPropagation);
elem.addEventListener("mouseup", StopPropagation);
elem.addEventListener("keydown", StopKeyPropagation);
elem.addEventListener("keyup", StopKeyPropagation);
elem.addEventListener("click", e=>{
e.stopPropagation();
this._PostToRuntimeElementMaybeSync("click", elementId)
}
);
elem.addEventListener("dblclick", e=>{
e.stopPropagation();
this._PostToRuntimeElementMaybeSync("dblclick", elementId)
}
);
elem.addEventListener("input", ()=>this.PostToRuntimeElement("change", elementId, {
"text": elem.value
}));
if (e["id"])
elem.id = e["id"];
this.UpdateState(elem, e);
return elem
}
UpdateState(elem, e) {
elem.value = e["text"];
elem.placeholder = e["placeholder"];
elem.title = e["title"];
elem.disabled = !e["isEnabled"];
elem.readOnly = e["isReadOnly"];
elem.spellcheck = e["spellCheck"];
const maxLength = e["maxLength"];
if (maxLength < 0)
elem.removeAttribute("maxlength");
else
elem.setAttribute("maxlength", maxLength)
}
_OnScrollToBottom(elem) {
elem.scrollTop = elem.scrollHeight
}
}
;
self.RuntimeInterface.AddDOMHandlerClass(HANDLER_CLASS)
}
;