re_lpc_lobby_custom/custom_lobby/static/js/coherent.js
2025-06-26 18:54:56 -03:00

721 lines
20 KiB
JavaScript

/*jslint browser: true, nomen: true, plusplus: true */
/// @file coherent.js
/// @namespace engine
/// Coherent UI JavaScript interface.
/// The `engine` module contains all functions for communication between the UI and the game / application.
(function (factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(global, global.engine, false);
} else {
window.engine = factory(window, window.engine, true);
}
})(function (global, engine, hasOnLoad) {
'use strict';
var VERSION = [1, 10, 2, 0];
/**
* Event emitter
*
* @class Emitter
*/
function Emitter() {
this.events = {};
}
function Handler(code, context) {
this.code = code;
this.context = context;
}
Emitter.prototype._createClear = function (object, name, handler) {
return function() {
var handlers = object.events[name];
if (handlers) {
var index = -1;
// this was in native previously
if(handler === undefined)
{
for(var i = 0; i < handlers.length; ++i)
{
if(handlers[i].wasInCPP !== undefined)
{
index = i;
break;
}
}
}
else
{
index = handlers.indexOf(handler);
}
if (index != -1) {
handlers.splice(index, 1);
if (handlers.length === 0) {
delete object.events[name];
}
}
} else {
if(engine.RemoveOnHandler !== undefined) {
engine.RemoveOnHandler(name);
}
}
};
};
/// @file coherent.js
/**
* Add a handler for an event
*
* @method on
* @param name the event name
* @param callback function to be called when the event is triggered
* @param context this binding for executing the handler, defaults to the Emitter
* @return connection object
*/
Emitter.prototype.on = function (name, callback, context) {
var handlers = this.events[name];
if (handlers === undefined)
handlers = this.events[name] = [];
var handler = new Handler(callback, context || this);
handlers.push(handler);
return { clear: this._createClear(this, name, handler) };
};
/**
* Remove a handler from an event
*
* @method off
* @param name the event name
* @param callback function to be called when the event is triggered
* @param context this binding for executing the handler, defaults to the Emitter
* @return connection object
*/
Emitter.prototype.off = function (name, handler, context) {
var handlers = this.events[name];
if (handlers !== undefined) {
context = context || this;
var index;
var length = handlers.length;
for (index = 0; index < length; ++index) {
var reg = handlers[index];
if (reg.code == handler && reg.context == context) {
break;
}
}
if (index < length) {
handlers.splice(index, 1);
if (handlers.length === 0) {
delete this.events[name];
}
}
}
else
{
engine.RemoveOnHandler(name);
}
};
var isAttached = engine !== undefined;
engine = engine || {};
/// @var engine.isAttached
/// Indicates whether the script is currently running inside Coherent GT
engine.isAttached = isAttached;
/// @var engine.forceEnableMocking
/// Indicates whether mocking should be enabled despite running inside Coherent GT
engine.forceEnableMocking = global.engineForceEnableMocking || false;
/// @var engine.IsAttached
/// [DEPRECATED] Indicates whether the script is currently running inside Coherent GT
/// @warning This property is deprecated, please use engine.isAttached (with camelCase)
engine.IsAttached = engine.isAttached;
engine.onEventsReplayed = null;
Emitter.prototype.trigger = function(name) {
var handlers = this.events[name];
if (handlers !== undefined) {
var args = Array.prototype.slice.call(arguments, 1);
handlers.forEach(function (handler) {
handler.code.apply(handler.context, args);
});
}
};
Emitter.prototype.merge = function (emitter) {
var lhs = this.events,
rhs = emitter.events,
push = Array.prototype.push,
events;
for (var e in rhs) {
events = lhs[e] = lhs[e] || [];
push.apply(events, rhs[e]);
}
};
var pending = 'pending';
var fulfilled = 'fulfilled';
var broken = 'broken';
function callAsync(code, context, argument) {
var async = function () {
code.call(context, argument);
};
setTimeout(async);
}
function Promise () {
this.emitter = new Emitter();
this.state = pending;
this.result = null;
}
Promise.prototype.resolve = function (result) {
this.state = fulfilled;
this.result = result;
this.emitter.trigger(fulfilled, result);
};
Promise.prototype.reject = function (result) {
this.state = broken;
this.result = result;
this.emitter.trigger(broken, result);
};
Promise.prototype.success = function (code, context) {
if (this.state !== fulfilled) {
this.emitter.on(fulfilled, code, context);
} else {
callAsync(code, context || this, this.result);
}
return this;
};
Promise.prototype.always = function (code, context) {
this.success(code, context);
this.otherwise(code, context);
return this;
};
Promise.prototype.otherwise = function (code, context) {
if (this.state !== broken) {
this.emitter.on(broken, code, context);
} else {
callAsync(code, context || this, this.result);
}
return this;
};
Promise.prototype.merge = function (other) {
if (this.state === pending) {
this.emitter.merge(other.emitter);
} else {
var handlers = other.emitter.events[this.state];
var self = this;
if (handlers !== undefined) {
handlers.forEach(function (handler) {
handler.code.call(handler.context, self.result);
});
}
}
};
Promise.prototype.make_chain = function (handler, promise, ok) {
return function (result) {
var handlerResult;
try {
handlerResult = handler.code.call(handler.context, result);
if (handlerResult instanceof Promise) {
handlerResult.merge(promise);
} else if (this.state === ok) {
promise.resolve(handlerResult);
} else {
promise.reject(handlerResult);
}
} catch (error) {
promise.reject(error);
}
};
};
function makeDefaultHandler(promise) {
return function () {
return promise;
};
}
Promise.prototype.then = function (callback, errback) {
var promise = new Promise();
var handler = new Handler(callback || makeDefaultHandler(this), this);
this.success(this.make_chain(handler, promise, fulfilled), this);
var errorHandler = new Handler(errback || makeDefaultHandler(this), this);
this.otherwise(this.make_chain(errorHandler, promise, broken), this);
return promise;
};
if (!engine.isAttached || engine.forceEnableMocking) {
Emitter.prototype.on = function (name, callback, context) {
var handlers = this.events[name];
if (this.browserCallbackOn) {
this.browserCallbackOn(name, callback, context);
}
if (handlers === undefined) {
handlers = this.events[name] = [];
}
var handler = new Handler(callback, context || this);
handlers.push(handler);
return { clear: this._createClear(this, name, handler) };
};
Emitter.prototype.off = function (name, handler, context) {
var handlers = this.events[name];
if (handlers !== undefined) {
context = context || this;
var index;
var length = handlers.length;
for (index = 0; index < length; ++index) {
var reg = handlers[index];
if (reg.code == handler && reg.context == context) {
break;
}
}
if (index < length) {
handlers.splice(index, 1);
if (handlers.length === 0) {
delete this.events[name];
if (this.browserCallbackOff) {
this.browserCallbackOff(name, handler, context);
}
}
}
}
};
engine.SendMessage = function (name, id) {
var args = Array.prototype.slice.call(arguments, 2);
var deferred = engine._ActiveRequests[id];
delete engine._ActiveRequests[id];
var call = function () {
var mock = engine._mocks[name];
if (mock !== undefined) {
deferred.resolve(mock.apply(engine, args));
}
};
window.setTimeout(call, 16);
};
engine.TriggerEvent = function () {
var args = Array.prototype.slice.call(arguments);
var trigger = function () {
var mock = engine._mocks[args[0]];
if (mock !== undefined) {
mock.apply(engine, args.slice(1));
}
};
window.setTimeout(trigger, 16);
};
engine.BindingsReady = function () {
engine._OnReady();
};
engine.__observeLifetime = function () {
};
engine.beginEventRecording =
engine.endEventRecording =
engine.saveEventRecord = function() {
console.warning("Event recording will not work in the browser!");
};
engine._mocks = {};
engine._mockImpl = function (name, mock, isCppCall, isEvent) {
if (mock && isCppCall) {
this._mocks[name] = mock;
}
// Extract the name of the arguments from Function.prototype.toString
var functionStripped = mock.toString().replace("function " + mock.name + "(", "");
var rightParanthesis = functionStripped.indexOf(")");
var args = functionStripped.substr(0, rightParanthesis);
if (this.browserCallbackMock) {
this.browserCallbackMock(name,
args,
isCppCall,
Boolean(isEvent));
}
}
engine.mock = function (name, mock, isEvent) {
this._mockImpl(name, mock, true, isEvent);
};
// Mock the call to translate to always return the key
engine.translate = function (text) {
return text;
};
}
engine.events = {};
for (var property in Emitter.prototype) {
engine[property] = Emitter.prototype[property];
}
if (engine.isAttached && !engine.forceEnableMocking) {
engine.on = function (name, callback, context) {
var handlers = this.events[name];
if (handlers === undefined && engine.AddOrRemoveOnHandler !== undefined) {
// Check where to cache the handler
var prevEvent = engine.AddOrRemoveOnHandler(name, callback, context);
// handler cached in C++
if(prevEvent === undefined) {
return { clear: this._createClear(this, name, undefined) };
}
handlers = this.events[name] = [];
// Add the previous handler
var prevHandler = new Handler(prevEvent[0], prevEvent[1] || this);
prevHandler.wasInCPP = true;
handlers.push(prevHandler);
} else if (handlers === undefined) {
handlers = this.events[name] = [];
}
var handler = new Handler(callback, context || this);
handlers.push(handler);
return { clear: this._createClear(this, name, handler) };
}
}
/// @function engine.on
/// Register handler for and event
/// @param {String} name name of the event
/// @param {Function} callback callback function to be executed when the event has been triggered
/// @param context *this* context for the function, by default the engine object
/// @function engine.beginEventRecording
/// Begins recording all events triggered using View::TriggerEvent from the game
/// @function engine.endEventRecording
/// Ends event recording
/// @function engine.saveEventRecord
/// Saves the events recorded in between the last calls to engine.beginEventRecording and engine.endEventRecording to a file
/// @param {String} path The path to the file where to save the recorded events. Optional. Defaults to "eventRecord.json"
/// @function engine.replayEvents
/// Replays the events previously recorded and stored in path. If you need to be notified when all events
/// are replayed, assign a callback to engine.onEventsReplayed
/// @param {Number} timeScale The speed at which to replay the events (e.g. pass 2 to double the speed). Optional. Defaults to 1.
/// @param {String} path The path to the file the recorded events are stored. Optional. Defaults to "eventRecord.json"
/// @function engine.translate
/// Translates the given text by invoking the system's localization manager if one exists.
/// @param {text} text The text to translate.
/// @return {String} undefined if no localization manager is set or no translation exists, else returns the translated string
/// @function engine.reloadLocalization
/// Updates the text on all elements with the data-l10n-id attribute by calling engine.translate
/// @function engine.off
/// Remove handler for an event
/// @param {String} name name of the event, by default removes all events
/// @param {Function} callback the callback function to be removed, by default removes all callbacks for a given event
/// @param context *this* context for the function, by default all removes all callbacks, regardless of context
/// @warning Removing all handlers for `engine` will remove some *Coherent UI* internal events, breaking some functionality.
/// @function engine.trigger
/// Trigger an event
/// This function will trigger any C++ handler registered for this event with `Coherent::UI::View::RegisterForEvent`
/// @param {String} name name of the event
/// @param ... any extra arguments to be passed to the event handlers
engine._trigger = Emitter.prototype.trigger;
var concatArguments = Array.prototype.concat;
engine.trigger = function (name) {
this._trigger.apply(this, arguments);
this.TriggerEvent.apply(this, arguments);
if (this.events['all'] !== undefined) {
var allArguments = concatArguments.apply(['all'], arguments);
this._trigger.apply(this, allArguments);
}
};
/// @function engine.showOverlay
/// Shows the debugging overlay in the browser.
/// Will also work in Coherent GT only if *engineForceEnableMocking* is set to *true*.
engine.showOverlay = function () {};
/// @function engine.hideOverlay
/// Hides the debugging overlay in the browser.
/// Will also work in Coherent GT only if *engineForceEnableMocking* is set to *true*.
engine.hideOverlay = function () {};
/// @function engine.mock
/// Mocks a C++ function call with the specified function.
/// Will also work in Coherent GT only if *engineForceEnableMocking* is set to *true*.
/// @param {String} name name of the event
/// @param {Function} mock a function to be called in-place of your native binding
/// @param {Boolean} isEvent whether you are mocking an event or function call
if (engine.isAttached && !engine.forceEnableMocking) {
engine.mock = function (name, mock, isEvent) { };
}
engine._BindingsReady = false;
engine._WindowLoaded = false;
engine._RequestId = 0;
engine._ActiveRequests = {};
/// @function engine.createDeferred
/// Create a new deferred object.
/// Use this to create deferred / promises that can be used together with `engine.call`.
/// @return {Deferred} a new deferred object
/// @see @ref CustomizingPromises
engine.createDeferred = (global.engineCreateDeferred === undefined) ?
function () { return new Promise(); }
: global.engineCreateDeferred;
/// @function engine.call
/// Call asynchronously a C++ handler and retrieve the result
/// The C++ handler must have been registered with `Coherent::UI::View::BindCall`
/// @param {String} name name of the C++ handler to be called
/// @param ... any extra parameters to be passed to the C++ handler
/// @return {Deferred} deferred object whose promise is resolved with the result of the C++ handler
engine.call = function () {
engine._RequestId++;
var id = engine._RequestId;
var deferred = engine.createDeferred();
engine._ActiveRequests[id] = deferred;
var messageArguments = Array.prototype.slice.call(arguments);
messageArguments.splice(1, 0, id);
engine.SendMessage.apply(this, messageArguments);
return deferred;
};
engine._Result = function (requestId) {
var deferred = engine._ActiveRequests[requestId];
if (deferred !== undefined)
{
delete engine._ActiveRequests[requestId];
var resultArguments = Array.prototype.slice.call(arguments);
resultArguments.shift();
deferred.resolve.apply(deferred, resultArguments);
}
};
engine._Errors = [ 'Success', 'ArgumentType', 'NoSuchMethod', 'NoResult' ];
engine._ForEachError = function (errors, callback) {
var length = errors.length;
for (var i = 0; i < length; ++i) {
callback(errors[i].first, errors[i].second);
}
};
engine._MapErrors = function (errors) {
var length = errors.length;
for (var i = 0; i < length; ++i) {
errors[i].first = engine._Errors[errors[i].first];
}
};
engine._TriggerError = function (type, message) {
engine.trigger('Error', type, message);
};
engine._OnError = function (requestId, errors) {
engine._MapErrors(errors);
if (requestId === null || requestId === 0) {
engine._ForEachError(errors, engine._TriggerError);
}
else {
var deferred = engine._ActiveRequests[requestId];
delete engine._ActiveRequests[requestId];
deferred.reject(errors);
}
};
engine._eventHandles = {};
engine._Register = function (eventName) {
var trigger = (function (name, engine) {
return function () {
var eventArguments = [name];
eventArguments.push.apply(eventArguments, arguments);
engine.TriggerEvent.apply(this, eventArguments);
};
}(eventName, engine));
engine._eventHandles[eventName] = engine.on(eventName, trigger);
};
engine._removeEventThunk = function (name) {
var handle = engine._eventHandles[name];
handle.clear();
delete engine._eventHandles[name];
};
engine._Unregister = function (name) {
if (typeof name === 'string') {
engine._removeEventThunk(name);
} else {
name.forEach(engine._removeEventThunk, engine);
}
};
function createMethodStub(name) {
var stub = function() {
var args = Array.prototype.slice.call(arguments);
args.splice(0, 0, name, this._id);
return engine.call.apply(engine, args);
};
return stub;
}
engine._boundTypes = {};
engine._createInstance = function (args) {
var type = args[0],
id = args[1],
methods = args[2],
constructor = engine._boundTypes[type];
if (constructor === undefined) {
constructor = function (id) {
this._id = id;
};
constructor.prototype.__Type = type;
methods.forEach(function (name) {
constructor.prototype[name] = createMethodStub(type + '_' + name);
});
engine._boundTypes[type] = constructor;
}
var instance = new constructor(id);
engine.__observeLifetime(instance);
return instance;
}
engine._OnReady = function () {
engine._BindingsReady = true;
if (engine._WindowLoaded) {
engine.trigger('Ready');
}
};
engine._OnWindowLoaded = function () {
engine._WindowLoaded = true;
if (engine._BindingsReady) {
engine.trigger('Ready');
}
};
engine._ThrowError = function (error) {
var prependTab = function (s) { return "\t" + s; };
var errorString = error.name + ": " + error.message + "\n" +
error.stack.split("\n").map(prependTab).join("\n");
console.error(errorString);
};
if (hasOnLoad) {
global.addEventListener("load", function () {
engine._OnWindowLoaded();
});
} else {
engine._WindowLoaded = true;
}
engine._coherentGlobalCanvas = document.createElement('canvas');
engine._coherentGlobalCanvas.id = "coherentGlobalCanvas";
engine._coherentGlobalCanvas.width = 1;
engine._coherentGlobalCanvas.height = 1;
engine._coherentGlobalCanvas.style.zIndex = 0;
engine._coherentGlobalCanvas.style.position = "absolute";
engine._coherentGlobalCanvas.style.border = "0px solid";
engine._coherentLiveImageData = new Array();
engine._coherentCreateImageData = function(name, guid) {
var ctx = engine._coherentGlobalCanvas.getContext("2d");
var coherentImage = ctx.coherentCreateImageData(guid);
engine._coherentLiveImageData[name] = coherentImage;
}
engine._coherentUpdatedImageData = function(name) {
engine._coherentLiveImageData[name].coherentUpdate();
var canvases = document.getElementsByTagName('canvas');
for(var i = 0; i < canvases.length; ++i) {
if(canvases[i].onEngineImageDataUpdated != null) {
canvases[i].onEngineImageDataUpdated(name,
engine._coherentLiveImageData[name]);
}
}
}
engine.reloadLocalization = function () {
var localizedElements = document.querySelectorAll('[data-l10n-id]');
for (var i = 0; i < localizedElements.length; i++) {
var element = localizedElements.item(i);
var translated = engine.translate(element.dataset.l10nId);
if (!translated) {
var warning = "Failed to find translation for key: " + element.dataset.l10nId;
console.warn(warning);
} else {
element.textContent = translated;
}
}
};
engine.on("_coherentCreateImageData", engine._coherentCreateImageData);
engine.on("_coherentUpdatedImageData", engine._coherentUpdatedImageData);
engine.on('_Result', engine._Result, engine);
engine.on('_Register', engine._Register, engine);
engine.on('_Unregister', engine._Unregister, engine);
engine.on('_OnReady', engine._OnReady, engine);
engine.on('_OnError', engine._OnError, engine);
engine.on('__OnReplayRecordCompleted', function(jsonRecords) {
if (engine.onEventsReplayed) {
engine.onEventsReplayed();
}
});
engine.BindingsReady(VERSION[0], VERSION[1], VERSION[2], VERSION[3]);
return engine;
});