<commit_before>/* global define,module,setTimeout */

var Channel;

Channel = (function () {

    'use strict';

    var ChannelConstructor,
        channels;

    channels = { };

    /**
     * Calling the `Channel` function creates a new message channel over which messages can be sent.
     * The function adds methods to an object that allow for listening to and triggering events.
     * You can pass in an object to transform it into a message channel or a string to create a public named channel.
     */
    ChannelConstructor = function ChannelConstructor (target) { // eslint-disable-line no-shadow

        var addEvent,
            channel,
            emit,
            events,
            isPublic,
            locked,
            silenced;

        locked = silenced = false;

        addEvent = function (eventName, override) {

            if (typeof events[eventName] === "undefined" || override) {
                events[eventName] = {
                    "callbacks": [],
                    "silenced": false,
                    "locked": false
                };
            }

            return events[eventName];

        };

        /**
         * The `events` variable saves the names of registered events and associates those names
         * with arrays of callback functions to be called, when the event is triggered.
         */
        events = {};

        isPublic = typeof target === 'string';

        if (isPublic) {
            channels[target] = {};
            channel = channels[target];
        } else if (typeof target === 'object') {
            channel = target;
        } else {
            channel = this || {};
        }

        emit = function emit (type, data, async) { // eslint-disable-line no-shadow

            var asyncEvt,
                asyncScore,
                callback,
                index,
                len,
                list;

            if (silenced || (events[type] && events[type].silenced)) {
                return this;
            }

            list = (events[type] && events[type].callbacks) || [];
            len = list.length;
            index = 0;

            asyncEvt = async === false ? -1 : async || 0;

            for (; index < len; index = index + 1) {
                callback = list[index];
                if (!callback.silenced) {
                    asyncScore = callback.async + asyncEvt;
                    if ((callback.async === 0 && asyncEvt === 0) || asyncScore > 0 || asyncEvt === 1) {

                        setTimeout(

                            function () {

                                this.func.apply(this.context,
                                    // [ this ].concat(this.data)
                                    this.data.concat([ this ])
                                );

                            }.bind({
                                "func": callback.callbackFunction,
                                "context": callback.context,
                                "name": type,
                                "channel": channel,
                                "async": true,
                                "data": data
                            }),

                        0);

                    } else if (asyncScore < 0 || asyncEvt === -1) {
                        list[index].callbackFunction.apply(list[index].context,
                            data.concat([ {
                                "func": callback.callbackFunction,
                                "context": callback.context,
                                "name": type,
                                "channel": channel,
                                "async": false,
                                "data": data
                            } ])
                        );
                    }
                }
            }

            return this;

        };

        Object.assign(channel, {

             /**
             * With this method you can register callbacks to be executed when a certain event is triggered.
             *
             * @param {String} The name of the event is specified by a string. It doesn't matter,
             *                 whether this event name already exists or not.
             * @param {Function} The function to be called, when the event is triggered.
             * @param {Object} Optionally, you can provide a context for the callback function.
             * @param {boolean} A preference regarding whether this callback shall the executed asynchronously.
             *                  (Not a guarantee!)
             */
            "on": function on (type, func, ctx, async) { // eslint-disable-line id-length

                if (locked || (events[type] && events[type].locked)) {
                    return this;
                }

                addEvent(type).callbacks.push({
                    "callbackFunction": func,
                    "context": ctx,
                    "silenced": false,
                    "async": async === false ? -1 : async || 0
                });

                return this;

            },

            /**
             * This method does the same as `on` but it registers a callback that will only be executed once.
             *
             * @param {String} The name of the event is specified by a string. It doesn't matter,
             *                 whether this event name already exists or not.
             * @param {Function} The function to be called, when the event is triggered.
             * @param {Object} Optionally, you can provide a context for the callback function.
             * @param {boolean} A preference regarding whether this callback shall the executed
             *                  asynchronously. (Not a guarantee!)
             */
            "once": function once (type, func, ctx, async) {

                var callbackFunction,
                    called;

                called = false;

                callbackFunction = function () {
                    if (!called) {
                        func.apply(this, [].slice.call(arguments));
                        channel.off(type, callbackFunction);
                        called = true;
                    }
                };

                this.on(type, callbackFunction, ctx, async);

                return this;

            },

            /**
             * This method allows it to remove event listeners. If a reference to a function is given,
             * only that function is removed. If only the type is given, all callbacks are removed from that event.
             * If no arguments are passed, all callbacks on all events are removed.
             * On public channels, only the first option (with both a type and a function reference) is permitted.
             *
             * @param {String} Optional: The event name of the callbacks to be removed.
             * @param {Function} Optional: The callback function to be removed.
             */
            "off": function off (type, func) {

                var index,
                    typeIndex;

                if (locked === true) {
                    return this;
                }

                if (isPublic === false) {
                    if (typeof type === "undefined") {
                        for (typeIndex in events) {
                            if (events.hasOwnProperty(typeIndex)) {
                                events[typeIndex].callbacks = [];
                            }
                        }
                    } else if (events[type] && events[type].locked === false) {
                        if (typeof func === "undefined") {
                            events[type].callbacks = [];
                        } else {
                            for (index = 0; index < events[type].callbacks.length; index = index + 1) {
                                events[type].callbacks.splice(index, 1);
                            }
                        }
                    }

                    return this;

                }

                if (typeof type !== "undefined" && events[type] && typeof func !== "undefined") {
                    for (index = 0; index < events[type].callbacks.length; index = index + 1) {
                        if (events[type].callbacks[index].callbackFunction === func) {
                            events[type].callbacks.splice(index, 1);
                        }
                    }
                }

                return this;

            },

            /**
             * Calling this method triggers the specified event and will result in all registered callbacks being executed.
             * You should no rely on the order in which the callbacks are being invoked.
             *
             * @param {String} The name of the event to be triggered. Any additional arguments will be passed to
             *                 the callback function.
             */
            "emit": function (type) {
                return emit.call(this, type, Array.prototype.slice.call(arguments, 1));
            },

            /**
             * This method works like `emit` but guarantees synchronous execution of all callbacks for this event.
             *
             * @param {String} The name of the event to be triggered. Any additional arguments will be passed to
             *                 the callback function.
             */
            "emitSync": function (type) {
                return emit.call(this, type, Array.prototype.slice.call(arguments, 1), false);
            },

            /**
             * This method works like `emit` but guarantees asynchronous execution of all callbacks for this event.
             *
             * @param {String} The name of the event to be triggered. Any additional arguments will be passed to
             *                 the callback function.
             */
            "emitAsync": function (type) {
                return emit.call(this, type, Array.prototype.slice.call(arguments, 1), true);
            },

            /**
             * The `silence` method prevents any new messages from being sent over the message channel.
             * Affects the entire channel or specific events or even specific callbacks.
             * Does not affect public channels at all.
             *
             * @param {String} Optional: The name of the event that is meant to be silenced.
             *                 If no event type is specified, the whole channel will be silenced.
             * @param {Function} Optional: The function that is no longer to be executed when the event is triggered.
             *                   If no function is specified the whole event type will be silenced.
             */
            "silence": (function () {

                if (isPublic === true) {
                    return function () {
                        return this;
                    };
                } else {
                    return function silence (type, func) {

                        var callbackCount,
                            evt,
                            index;

                        if (typeof type === "undefined") {
                            silenced = true;

                            return this;
                        }

                        if (typeof func === "undefined") {
                            addEvent(type).silenced = true;

                            return this;
                        }

                        evt = events[type];

                        if (typeof evt !== "undefined") {
                            callbackCount = evt.callbacks.length;
                            for (index = 0; index < callbackCount; index = index + 1) {
                                if (evt.callbacks[index].callbackFunction === func) {
                                    evt.callbacks[index].silenced = true;
                                }
                            }
                        }

                        return this;

                    };
                }

            }()),

            /**
             * With this method you can enable message sending after it was disable using `silence`.
             *
             * This method does not check if the channel is public, because if it were, it could not
             * have been silenced in the first place.
             *
             * @param {String} Optional: The name of the event that is meant to be unsilenced.
             * @param {Function} Optional: The function that shall be executed again, after being silenced.
             */
            "unsilence": function unsilence (type, func) {

                var callbackCount,
                    evt,
                    index;

                if (typeof type === "undefined") {
                    silenced = false;

                    return this;
                }

                if (typeof func === "undefined") {
                    addEvent(type).silenced = false;

                    return this;
                }

                evt = events[type];

                if (typeof evt !== "undefined") {
                    callbackCount = evt.callbacks.length;
                    for (index = 0; index < callbackCount; index = index + 1) {
                        if (evt.callbacks[index].callbackFunction === func) {
                            evt.callbacks[index].silenced = false;
                        }
                    }
                }

                return this;

            },

            /**
             * `Lock` prevents new callbacks from being added. Affects the entire channel or specific events.
             * Does not affect public channels at all.
             *
             * @param {String} Optional: The name of the event to which no new callbacks shall be registerd.
             *                 If no event name is specified, the whole channel will be locked.
             */
            "lock": (function () {

                if (isPublic === true) {
                    return function () {
                        return this;
                    };
                } else {
                    return function lock (type) {

                        if (typeof type === "undefined") {
                            locked = true;
                        } else {
                            addEvent(type).locked = true;
                        }

                        return this;

                    };
                }

            }()),

            /**
             * Unlock allows callbacks from being added to a channel after it was locked.
             *
             * This method does not check if the channel is public, because if it were, it could not
             * have been locked in the first place.
             *
             * @param {String} Optional: The name of the event that shall accept new callbacks again.
             */
            "unlock": function unlock (type) {

                if (typeof type === "undefined") {
                    locked = false;
                } else {
                    addEvent(type).locked = false;
                }

                return this;

            },

            /**
             * This lets you remove all event listeners from the message channel or from a specified event type.
             * (Also sets `silenced` and `locked` to `false`).
             *
             * @param {String} Optional: The name of the event whose callbacks shall be removed.
             *                 If no event type is given, the whole channel will be reset.
             */
            "reset": (function () {

                if (isPublic === true) {
                    return function () {
                        return this;
                    };
                } else {
                    return function reset (type) {

                        if (events !== null) {
                            if (typeof type === "undefined") {
                                events = { };
                            } else {
                                addEvent(type, true);
                            }
                        }

                        return this;

                    };
                }

            }()),

            /**
             * Returns whether the channel or an event type or a specific callback is silenced.
             *
             * @param {String} Optional: The name of the event whose status is being requested.
             *                 If no event type is specified, the whole channel's status will be returned.
             * @param {Function} Optional: The function whose status is being requested.
             *                   If no function is specified the whole event type's status will be returned.
             */
            "isSilenced": function isSilenced (type, func) {

                return isPublic === false && (function () {

                    var callbackCount,
                        channelSilenced,
                        evt,
                        index;

                    channelSilenced = false;

                    if (typeof type === "undefined" || silenced) {
                        channelSilenced = silenced;
                    } else if (events[type] && (typeof func === "undefined" || events[type].silenced)) {
                        channelSilenced = events[type].silenced;
                    } else {
                        evt = events[type];
                        if (typeof evt !== "undefined") {
                            callbackCount = evt.callbacks.length;
                            for (index = 0; index < callbackCount; index = index + 1) {
                                if (evt.callbacks[index].callbackFunction === func) {
                                    channelSilenced = evt.callbacks[index].silenced;
                                    break;
                                }
                            }
                        }
                    }

                    return channelSilenced;

                }());

            },

            /**
             * Returns whether the channel or an event type is locked.
             *
             * @param {String} Optional: The name of the event whose status is being requested.
             *                 If no event type is specified, the whole channel's status will be returned.
             */
            "isLocked": function isLocked (type) {

                return isPublic === false && (function () {

                    var channelLocked;

                    if (typeof type === "undefined" || locked) {
                        channelLocked = locked;
                    } else {
                        channelLocked = (events[type] || false) && events[type].locked;
                    }

                    return channelLocked;

                }());

            }

        });

        /**
         * These aliases get added to give you the best expressability.
         * You can currently only delete them by creating a new channel and `delete channel.subscribe` etc.
         */
        channel.subscribe = channel.attach = channel.on;
        channel.unsubscribe = channel.detach = channel.off;
        channel.publish = channel.fire = channel.trigger = channel.emit;
        channel.publishSync = channel.fireSync = channel.triggerSync = channel.emitSync;
        channel.publishAsync = channel.fireAsync = channel.triggerAsync = channel.emitAsync;

        return channel;

    };

    /**
     * This method returns a named channel.
     *
     * @param {String} The name of the public channel you want to retrieve.
     * @return The public channel with the specified name
     */
    ChannelConstructor.get = function (name) {
        return channels[name];
    };

    return ChannelConstructor;

}());

// see UMD pattern at https://github.com/umdjs/umd/blob/master/returnExports.js
(function (root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.Channel = factory();
    }
}(this, function () {
    'use strict';
    return Channel;
}));
<commit_msg>Fixed 3 more eslint warnings<commit_after>/* global define,module,setTimeout */

var Channel;

Channel = (function () {

    'use strict';

    var ChannelConstructor,
        channels;

    channels = { };

    /**
     * Calling the `Channel` function creates a new message channel over which messages can be sent.
     * The function adds methods to an object that allow for listening to and triggering events.
     * You can pass in an object to transform it into a message channel or a string to create a public named channel.
     */
    ChannelConstructor = function ChannelConstructor (target) { // eslint-disable-line no-shadow

        var addEvent,
            channel,
            emit,
            events,
            isPublic,
            locked,
            silenced;

        locked = silenced = false;

        addEvent = function (eventName, override) {

            if (typeof events[eventName] === "undefined" || override) {
                events[eventName] = {
                    "callbacks": [],
                    "silenced": false,
                    "locked": false
                };
            }

            return events[eventName];

        };

        /**
         * The `events` variable saves the names of registered events and associates those names
         * with arrays of callback functions to be called, when the event is triggered.
         */
        events = {};

        isPublic = typeof target === 'string';

        if (isPublic) {
            channels[target] = {};
            channel = channels[target];
        } else if (typeof target === 'object') {
            channel = target;
        } else {
            channel = this || {};
        }

        emit = function emit (type, data, async) { // eslint-disable-line no-shadow

            var asyncEvt,
                asyncScore,
                callback,
                index,
                len,
                list;

            if (silenced || (events[type] && events[type].silenced)) {
                return channel;
            }

            list = (events[type] && events[type].callbacks) || [];
            len = list.length;
            index = 0;

            asyncEvt = async === false ? -1 : async || 0;

            for (; index < len; index = index + 1) {
                callback = list[index];
                if (!callback.silenced) {
                    asyncScore = callback.async + asyncEvt;
                    if ((callback.async === 0 && asyncEvt === 0) || asyncScore > 0 || asyncEvt === 1) {

                        setTimeout(

                            function () {

                                this.func.apply(this.context,
                                    this.data.concat([ this ])
                                );

                            }.bind({
                                "func": callback.callbackFunction,
                                "context": callback.context,
                                "name": type,
                                "channel": channel,
                                "async": true,
                                "data": data
                            }),

                        0);

                    } else if (asyncScore < 0 || asyncEvt === -1) {
                        list[index].callbackFunction.apply(list[index].context,
                            data.concat([ {
                                "func": callback.callbackFunction,
                                "context": callback.context,
                                "name": type,
                                "channel": channel,
                                "async": false,
                                "data": data
                            } ])
                        );
                    }
                }
            }

            return channel;

        };

        Object.assign(channel, {

             /**
             * With this method you can register callbacks to be executed when a certain event is triggered.
             *
             * @param {String} The name of the event is specified by a string. It doesn't matter,
             *                 whether this event name already exists or not.
             * @param {Function} The function to be called, when the event is triggered.
             * @param {Object} Optionally, you can provide a context for the callback function.
             * @param {boolean} A preference regarding whether this callback shall the executed asynchronously.
             *                  (Not a guarantee!)
             */
            "on": function on (type, func, ctx, async) { // eslint-disable-line id-length

                if (locked || (events[type] && events[type].locked)) {
                    return this;
                }

                addEvent(type).callbacks.push({
                    "callbackFunction": func,
                    "context": ctx,
                    "silenced": false,
                    "async": async === false ? -1 : async || 0
                });

                return this;

            },

            /**
             * This method does the same as `on` but it registers a callback that will only be executed once.
             *
             * @param {String} The name of the event is specified by a string. It doesn't matter,
             *                 whether this event name already exists or not.
             * @param {Function} The function to be called, when the event is triggered.
             * @param {Object} Optionally, you can provide a context for the callback function.
             * @param {boolean} A preference regarding whether this callback shall the executed
             *                  asynchronously. (Not a guarantee!)
             */
            "once": function once (type, func, ctx, async) {

                var callbackFunction,
                    called;

                called = false;

                callbackFunction = function () {
                    if (!called) {
                        func.apply(this, [].slice.call(arguments));
                        channel.off(type, callbackFunction);
                        called = true;
                    }
                };

                this.on(type, callbackFunction, ctx, async);

                return this;

            },

            /**
             * This method allows it to remove event listeners. If a reference to a function is given,
             * only that function is removed. If only the type is given, all callbacks are removed from that event.
             * If no arguments are passed, all callbacks on all events are removed.
             * On public channels, only the first option (with both a type and a function reference) is permitted.
             *
             * @param {String} Optional: The event name of the callbacks to be removed.
             * @param {Function} Optional: The callback function to be removed.
             */
            "off": function off (type, func) {

                var index,
                    typeIndex;

                if (locked === true) {
                    return this;
                }

                if (isPublic === false) {
                    if (typeof type === "undefined") {
                        for (typeIndex in events) {
                            if (events.hasOwnProperty(typeIndex)) {
                                events[typeIndex].callbacks = [];
                            }
                        }
                    } else if (events[type] && events[type].locked === false) {
                        if (typeof func === "undefined") {
                            events[type].callbacks = [];
                        } else {
                            for (index = 0; index < events[type].callbacks.length; index = index + 1) {
                                events[type].callbacks.splice(index, 1);
                            }
                        }
                    }

                    return this;

                }

                if (typeof type !== "undefined" && events[type] && typeof func !== "undefined") {
                    for (index = 0; index < events[type].callbacks.length; index = index + 1) {
                        if (events[type].callbacks[index].callbackFunction === func) {
                            events[type].callbacks.splice(index, 1);
                        }
                    }
                }

                return this;

            },

            /**
             * Calling this method triggers the specified event and will result in all registered callbacks being executed.
             * You should no rely on the order in which the callbacks are being invoked.
             *
             * @param {String} The name of the event to be triggered. Any additional arguments will be passed to
             *                 the callback function.
             */
            "emit": function (type) {
                return emit.call(this, type, Array.prototype.slice.call(arguments, 1));
            },

            /**
             * This method works like `emit` but guarantees synchronous execution of all callbacks for this event.
             *
             * @param {String} The name of the event to be triggered. Any additional arguments will be passed to
             *                 the callback function.
             */
            "emitSync": function (type) {
                return emit.call(this, type, Array.prototype.slice.call(arguments, 1), false);
            },

            /**
             * This method works like `emit` but guarantees asynchronous execution of all callbacks for this event.
             *
             * @param {String} The name of the event to be triggered. Any additional arguments will be passed to
             *                 the callback function.
             */
            "emitAsync": function (type) {
                return emit.call(this, type, Array.prototype.slice.call(arguments, 1), true);
            },

            /**
             * The `silence` method prevents any new messages from being sent over the message channel.
             * Affects the entire channel or specific events or even specific callbacks.
             * Does not affect public channels at all.
             *
             * @param {String} Optional: The name of the event that is meant to be silenced.
             *                 If no event type is specified, the whole channel will be silenced.
             * @param {Function} Optional: The function that is no longer to be executed when the event is triggered.
             *                   If no function is specified the whole event type will be silenced.
             */
            "silence": (function () {

                if (isPublic === true) {
                    return function () {
                        return this;
                    };
                } else {
                    return function silence (type, func) {

                        var callbackCount,
                            evt,
                            index;

                        if (typeof type === "undefined") {
                            silenced = true;

                            return this;
                        }

                        if (typeof func === "undefined") {
                            addEvent(type).silenced = true;

                            return this;
                        }

                        evt = events[type];

                        if (typeof evt !== "undefined") {
                            callbackCount = evt.callbacks.length;
                            for (index = 0; index < callbackCount; index = index + 1) {
                                if (evt.callbacks[index].callbackFunction === func) {
                                    evt.callbacks[index].silenced = true;
                                }
                            }
                        }

                        return this;

                    };
                }

            }()),

            /**
             * With this method you can enable message sending after it was disable using `silence`.
             *
             * This method does not check if the channel is public, because if it were, it could not
             * have been silenced in the first place.
             *
             * @param {String} Optional: The name of the event that is meant to be unsilenced.
             * @param {Function} Optional: The function that shall be executed again, after being silenced.
             */
            "unsilence": function unsilence (type, func) {

                var callbackCount,
                    evt,
                    index;

                if (typeof type === "undefined") {
                    silenced = false;

                    return this;
                }

                if (typeof func === "undefined") {
                    addEvent(type).silenced = false;

                    return this;
                }

                evt = events[type];

                if (typeof evt !== "undefined") {
                    callbackCount = evt.callbacks.length;
                    for (index = 0; index < callbackCount; index = index + 1) {
                        if (evt.callbacks[index].callbackFunction === func) {
                            evt.callbacks[index].silenced = false;
                        }
                    }
                }

                return this;

            },

            /**
             * `Lock` prevents new callbacks from being added. Affects the entire channel or specific events.
             * Does not affect public channels at all.
             *
             * @param {String} Optional: The name of the event to which no new callbacks shall be registerd.
             *                 If no event name is specified, the whole channel will be locked.
             */
            "lock": (function () {

                if (isPublic === true) {
                    return function () {
                        return this;
                    };
                } else {
                    return function lock (type) {

                        if (typeof type === "undefined") {
                            locked = true;
                        } else {
                            addEvent(type).locked = true;
                        }

                        return this;

                    };
                }

            }()),

            /**
             * Unlock allows callbacks from being added to a channel after it was locked.
             *
             * This method does not check if the channel is public, because if it were, it could not
             * have been locked in the first place.
             *
             * @param {String} Optional: The name of the event that shall accept new callbacks again.
             */
            "unlock": function unlock (type) {

                if (typeof type === "undefined") {
                    locked = false;
                } else {
                    addEvent(type).locked = false;
                }

                return this;

            },

            /**
             * This lets you remove all event listeners from the message channel or from a specified event type.
             * (Also sets `silenced` and `locked` to `false`).
             *
             * @param {String} Optional: The name of the event whose callbacks shall be removed.
             *                 If no event type is given, the whole channel will be reset.
             */
            "reset": (function () {

                if (isPublic === true) {
                    return function () {
                        return this;
                    };
                } else {
                    return function reset (type) {

                        if (events !== null) {
                            if (typeof type === "undefined") {
                                events = { };
                            } else {
                                addEvent(type, true);
                            }
                        }

                        return this;

                    };
                }

            }()),

            /**
             * Returns whether the channel or an event type or a specific callback is silenced.
             *
             * @param {String} Optional: The name of the event whose status is being requested.
             *                 If no event type is specified, the whole channel's status will be returned.
             * @param {Function} Optional: The function whose status is being requested.
             *                   If no function is specified the whole event type's status will be returned.
             */
            "isSilenced": function isSilenced (type, func) {

                return isPublic === false && (function () {

                    var callbackCount,
                        channelSilenced,
                        evt,
                        index;

                    channelSilenced = false;

                    if (typeof type === "undefined" || silenced) {
                        channelSilenced = silenced;
                    } else if (events[type] && (typeof func === "undefined" || events[type].silenced)) {
                        channelSilenced = events[type].silenced;
                    } else {
                        evt = events[type];
                        if (typeof evt !== "undefined") {
                            callbackCount = evt.callbacks.length;
                            for (index = 0; index < callbackCount; index = index + 1) {
                                if (evt.callbacks[index].callbackFunction === func) {
                                    channelSilenced = evt.callbacks[index].silenced;
                                    break;
                                }
                            }
                        }
                    }

                    return channelSilenced;

                }());

            },

            /**
             * Returns whether the channel or an event type is locked.
             *
             * @param {String} Optional: The name of the event whose status is being requested.
             *                 If no event type is specified, the whole channel's status will be returned.
             */
            "isLocked": function isLocked (type) {

                return isPublic === false && (function () {

                    var channelLocked;

                    if (typeof type === "undefined" || locked) {
                        channelLocked = locked;
                    } else {
                        channelLocked = (events[type] || false) && events[type].locked;
                    }

                    return channelLocked;

                }());

            }

        });

        /**
         * These aliases get added to give you the best expressability.
         * You can currently only delete them by creating a new channel and `delete channel.subscribe` etc.
         */
        channel.subscribe = channel.attach = channel.on;
        channel.unsubscribe = channel.detach = channel.off;
        channel.publish = channel.fire = channel.trigger = channel.emit;
        channel.publishSync = channel.fireSync = channel.triggerSync = channel.emitSync;
        channel.publishAsync = channel.fireAsync = channel.triggerAsync = channel.emitAsync;

        return channel;

    };

    /**
     * This method returns a named channel.
     *
     * @param {String} The name of the public channel you want to retrieve.
     * @return The public channel with the specified name
     */
    ChannelConstructor.get = function (name) {
        return channels[name];
    };

    return ChannelConstructor;

}());

// see UMD pattern at https://github.com/umdjs/umd/blob/master/returnExports.js
(function (root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.Channel = factory();
    }
}(this, function () {
    'use strict';
    return Channel;
}));
<|endoftext|>


<commit_before>app.directive('selectLang', ['$rootScope', 'Languages', 'ngIfDirective', function ($rootScope, Languages, ngIfDirective) {
   var ngIf = ngIfDirective[0];
   return {
      transclude: ngIf.transclude,
      priority: ngIf.priority-1,
      terminal: ngIf.terminal,
      restrict: ngIf.restrict,
      link: function(scope, elem, attrs) {
         scope.Languages = Languages;
         var targetLang = attrs['selectLang'];
         var conditions = ['!Languages.currentLanguage'];
         var allLangs = targetLang.split(' ');
         for (var i=0; i<allLangs.length; i++) {
            conditions.push('Languages.currentLanguage == "' + allLangs[i] + '"');
         }
         var newCond = conditions.join(' || ');
         if(attrs.ngIf) {
            attrs.ngIf = '(' + attrs.ngIf + ') && (' + newCond + ')';
         } else {
            attrs.ngIf = newCond;
         }
         ngIf.link.apply(ngIf, arguments);
      }
   };
}]);

app.directive('selectLangSelector', ['$rootScope', 'Languages', function ($rootScope, Languages) {
   return {
      scope: false,
      restrict: 'EA',
      template: '<select ng-model="Languages.currentLanguage"><option ng-repeat="opt in selectLangs" value="{{ opt.id }}">{{ opt.label }}</option></select>',
      link: function(scope, elem, attrs) {
         scope.selectLangs = Languages.sourceLanguages;
         scope.Languages = Languages;
         scope.$on('fioi-editor2.languageChanged', function(e, lang) {
            if(lang == 'text') { return; }
            Languages.currentLanguage = lang;
            });
      }
   };
}]);
<commit_msg>selectLang: Display java elements for java8 too<commit_after>app.directive('selectLang', ['$rootScope', 'Languages', 'ngIfDirective', function ($rootScope, Languages, ngIfDirective) {
   var ngIf = ngIfDirective[0];
   return {
      transclude: ngIf.transclude,
      priority: ngIf.priority-1,
      terminal: ngIf.terminal,
      restrict: ngIf.restrict,
      link: function(scope, elem, attrs) {
         scope.Languages = Languages;
         var targetLang = attrs['selectLang'];
         var conditions = ['!Languages.currentLanguage'];
         var allLangs = targetLang.split(' ');
         for (var i=0; i<allLangs.length; i++) {
            // Special case for java/java8
            // TODO :: find a better way to handle it
            if(allLangs[i] == 'java') {
               conditions.push('Languages.currentLanguage == "java8"');
            }
            conditions.push('Languages.currentLanguage == "' + allLangs[i] + '"');
         }
         var newCond = conditions.join(' || ');
         if(attrs.ngIf) {
            attrs.ngIf = '(' + attrs.ngIf + ') && (' + newCond + ')';
         } else {
            attrs.ngIf = newCond;
         }
         ngIf.link.apply(ngIf, arguments);
      }
   };
}]);

app.directive('selectLangSelector', ['$rootScope', 'Languages', function ($rootScope, Languages) {
   return {
      scope: false,
      restrict: 'EA',
      template: '<select ng-model="Languages.currentLanguage"><option ng-repeat="opt in selectLangs" value="{{ opt.id }}">{{ opt.label }}</option></select>',
      link: function(scope, elem, attrs) {
         scope.selectLangs = Languages.sourceLanguages;
         scope.Languages = Languages;
         scope.$on('fioi-editor2.languageChanged', function(e, lang) {
            if(lang == 'text') { return; }
            Languages.currentLanguage = lang;
            });
      }
   };
}]);
<|endoftext|>


<commit_before>require(
['ext/jquery', 'ext/underscore', 'ext/underscore.string', 'course', 'ext/bootstrap', 'ext/backbone'],
function($, _, _s, course, _b, Backbone) {
  $(function() {

    var CourseSearchView = Backbone.View.extend({
      className: 'course-search',
      timer: undefined,
      keywords: undefined,
      sortMode: undefined,
      direction: undefined,
      count: 10,
      offset: 0,
      courseCollection: undefined,
      hasMore: true,

      initialize: function(options) {
        this.sortMode = window.pageData.sortModes[0];
        this.setDirection(this.sortMode.direction);
        this.courseCollection = new course.CourseCollection();
        $(window).scroll(_.bind(this.scrollWindow, this));
      },

      scrollWindow: function(evt) {
        var loaderOffset = this.$('.loader-container').offset().top;
        var $window = $(window);
        var bottomOffset = $window.scrollTop() + $window.height();
        if (bottomOffset > loaderOffset) {
          this.updateCourses();
        }
      },

      render: function() {
        var sortModes = window.pageData.sortModes;
        this.$el.html(_.template($('#search-form-tpl').html(), {
          sortModes: sortModes,
          selectedSortMode: this.sortMode,
          selectedDirection: this.direction
        }));
        $('.dropdown-toggle').dropdown();
        var courseCollectionView = new course.CourseCollectionView({
          courseCollection: this.courseCollection
        });
        this.$('.course-collection-placeholder').replaceWith(courseCollectionView.render().$el);
        this.updateCourses();

        return this;
      },

      events: {
        'click .sort-mode-dropdown .dropdown-menu li': 'changeSortMode',
        'click .order-dropdown .dropdown-menu li': 'changeDirection',
        'input .keywords': 'changeKeywords',
        'paste .keywords': 'changeKeywords'
      },

      changeSortMode: function(evt) {
        var $target = $(evt.currentTarget);
        this.$('.selected-sort-mode').text($target.text());
        var sortValue = $target.attr('data-value');
        this.sortMode = _.find(window.pageData.sortModes, function(sortMode) {
          return sortValue === sortMode.value;
        }, this);
        this.setDirection(this.sortMode.direction);
        this.$('.selected-direction').text(this.direction.name);

        this.resetCourses();
        this.updateCourses();
      },

      changeDirection: function(evt) {
        var $target = $(evt.currentTarget);
        this.$('.selected-direction').text($target.text());
        var directionValue = window.parseInt($target.attr('data-value'), 10);
        this.setDirection(directionValue);

        this.resetCourses();
        this.updateCourses();
      },

      changeKeywords: function(evt) {
        var $target = $(evt.currentTarget);
        this.keywords = $target.val();

        if (this.timer) {
          // Prevent multiple API searches if entering multiple characters
          window.clearTimeout(this.timer);
        }

        this.resetCourses();
        this.timer = window.setTimeout(_.bind(this.updateCourses, this), 500);
      },

      setDirection: function(direction) {
        var directionName = undefined;
        if (direction > 0) {
          directionName = 'ascending';
        } else {
          directionName = 'descending';
        }
        this.direction = {
          'value': direction,
          'name': directionName
        }
      },

      resetCourses: function() {
        this.courseCollection.reset();
        this.hasMore = true;
        this.offset = 0;
      },

      updateCourses: function() {
        if (!this.hasMore || this.updatingCourses) {
          // TODO(mack): handle case of very short interval between changing
          // search options
          return;
        }
        this.$('.loader').removeClass('hide');
        this.updatingCourses = true;
        // TODO(mack): use $.ajax to handle error
        var args = [];
        args.push('offset=' + this.offset);
        args.push('count=' + this.count);
        if (this.sortMode) {
          args.push('sort_mode=' + this.sortMode.value);
        }
        if (this.direction) {
          args.push('direction=' + this.direction.value);
        }
        if (this.keywords) {
          args.push('keywords=' + this.keywords);
        }
        $.getJSON(
          '/api/course-search?' + args.join('&'),
          _.bind(function(data) {
            var courses = data.courses;
            this.hasMore = data.has_more;
            this.offset += courses.length;
            this.courseCollection.add(courses);
            this.updatingCourses = false;
            this.$('.loader').addClass('hide');
          }, this)
        );
      }
    });

    var init = function() {
      (function() {
        var courseSearchView = new CourseSearchView({});
        $('#course-page-container').append(courseSearchView.render().$el);
      })();

      (function() {
      })();
    };

    init();
  });
});
<commit_msg>auto focus filter box on course page<commit_after>require(
['ext/jquery', 'ext/underscore', 'ext/underscore.string', 'course', 'ext/bootstrap', 'ext/backbone'],
function($, _, _s, course, _b, Backbone) {
  $(function() {

    var CourseSearchView = Backbone.View.extend({
      className: 'course-search',
      timer: undefined,
      keywords: undefined,
      sortMode: undefined,
      direction: undefined,
      count: 10,
      offset: 0,
      courseCollection: undefined,
      hasMore: true,

      initialize: function(options) {
        this.sortMode = window.pageData.sortModes[0];
        this.setDirection(this.sortMode.direction);
        this.courseCollection = new course.CourseCollection();
        $(window).scroll(_.bind(this.scrollWindow, this));
      },

      scrollWindow: function(evt) {
        var loaderOffset = this.$('.loader-container').offset().top;
        var $window = $(window);
        var bottomOffset = $window.scrollTop() + $window.height();
        if (bottomOffset > loaderOffset) {
          this.updateCourses();
        }
      },

      render: function() {
        var sortModes = window.pageData.sortModes;
        this.$el.html(_.template($('#search-form-tpl').html(), {
          sortModes: sortModes,
          selectedSortMode: this.sortMode,
          selectedDirection: this.direction
        }));

        $('.dropdown-toggle').dropdown();
        var courseCollectionView = new course.CourseCollectionView({
          courseCollection: this.courseCollection
        });
        this.$('.course-collection-placeholder').replaceWith(courseCollectionView.render().$el);
        this.updateCourses();

        window.setTimeout(_.bind(function() {
          // TODO(mack): investgate why this has to be done in window.setTimeout
          this.$('.keywords').focus();
        }, this), 0);

        return this;
      },

      events: {
        'click .sort-mode-dropdown .dropdown-menu li': 'changeSortMode',
        'click .order-dropdown .dropdown-menu li': 'changeDirection',
        'input .keywords': 'changeKeywords',
        'paste .keywords': 'changeKeywords'
      },

      changeSortMode: function(evt) {
        var $target = $(evt.currentTarget);
        this.$('.selected-sort-mode').text($target.text());
        var sortValue = $target.attr('data-value');
        this.sortMode = _.find(window.pageData.sortModes, function(sortMode) {
          return sortValue === sortMode.value;
        }, this);
        this.setDirection(this.sortMode.direction);
        this.$('.selected-direction').text(this.direction.name);

        this.resetCourses();
        this.updateCourses();
      },

      changeDirection: function(evt) {
        var $target = $(evt.currentTarget);
        this.$('.selected-direction').text($target.text());
        var directionValue = window.parseInt($target.attr('data-value'), 10);
        this.setDirection(directionValue);

        this.resetCourses();
        this.updateCourses();
      },

      changeKeywords: function(evt) {
        var $target = $(evt.currentTarget);
        this.keywords = $target.val();

        if (this.timer) {
          // Prevent multiple API searches if entering multiple characters
          window.clearTimeout(this.timer);
        }

        this.resetCourses();
        this.timer = window.setTimeout(_.bind(this.updateCourses, this), 500);
      },

      setDirection: function(direction) {
        var directionName = undefined;
        if (direction > 0) {
          directionName = 'ascending';
        } else {
          directionName = 'descending';
        }
        this.direction = {
          'value': direction,
          'name': directionName
        }
      },

      resetCourses: function() {
        this.courseCollection.reset();
        this.hasMore = true;
        this.offset = 0;
      },

      updateCourses: function() {
        if (!this.hasMore || this.updatingCourses) {
          // TODO(mack): handle case of very short interval between changing
          // search options
          return;
        }
        this.$('.loader').removeClass('hide');
        this.updatingCourses = true;
        // TODO(mack): use $.ajax to handle error
        var args = [];
        args.push('offset=' + this.offset);
        args.push('count=' + this.count);
        if (this.sortMode) {
          args.push('sort_mode=' + this.sortMode.value);
        }
        if (this.direction) {
          args.push('direction=' + this.direction.value);
        }
        if (this.keywords) {
          args.push('keywords=' + this.keywords);
        }
        $.getJSON(
          '/api/course-search?' + args.join('&'),
          _.bind(function(data) {
            var courses = data.courses;
            this.hasMore = data.has_more;
            this.offset += courses.length;
            this.courseCollection.add(courses);
            this.updatingCourses = false;
            this.$('.loader').addClass('hide');
          }, this)
        );
      }
    });

    var init = function() {
      (function() {
        var courseSearchView = new CourseSearchView({});
        $('#course-page-container').append(courseSearchView.render().$el);
      })();

      (function() {
      })();
    };

    init();
  });
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 */

/* global __DEV__ */
/* global __UPLOAD_API__ */
/* global __API_HOST__ */
/* global __INVITE_KEY__ */
/* global __LATEST_TERMS__ */
/* global __PASSWORD_MIN_LENGTH__ */
/* global __PASSWORD_MAX_LENGTH__ */
/* global __ABOUT_MAX_LENGTH__ */
/* global __I18N_ENABLED__ */
/* global __RX_ENABLED__ */
/* global __CLINICS_ENABLED__ */
/* global __VERSION__ */

function booleanFromText(value, defaultValue) {
  if (value === 'true' || value === true) {
    return true;
  }

  if (value === 'false') {
    return false;
  }

  return defaultValue || false;
}

function integerFromText(value, defaultValue) {
  value = parseInt(value, 10);
  if (isNaN(value)) {
    return defaultValue === undefined ? 0 : defaultValue;
  }
  return value;
}

const config = {
  VERSION: __VERSION__,
  UPLOAD_API: __UPLOAD_API__ || 'https://tidepool.org/uploader',
  API_HOST: __API_HOST__ || `${window.location.protocol}//${window.location.host}`,
  INVITE_KEY: __INVITE_KEY__ || '',
  LATEST_TERMS: __LATEST_TERMS__ || null,
  PASSWORD_MIN_LENGTH: integerFromText(__PASSWORD_MIN_LENGTH__, 8),
  PASSWORD_MAX_LENGTH: integerFromText(__PASSWORD_MAX_LENGTH__, 72),
  ABOUT_MAX_LENGTH: integerFromText(__ABOUT_MAX_LENGTH__, 256),
  I18N_ENABLED: booleanFromText(__I18N_ENABLED__, false),
  RX_ENABLED: booleanFromText(__RX_ENABLED__, false),
  CLINICS_ENABLED: booleanFromText(__CLINICS_ENABLED__, false),
}

if (__DEV__) {
  window.config = config;
}

// the constants below are defined in webpack.config.js -- they're aliases for
// environment variables.
module.exports = config;
<commit_msg>Small change to booleanFromText handling to accommodate local.js config<commit_after>/**
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 */

/* global __DEV__ */
/* global __UPLOAD_API__ */
/* global __API_HOST__ */
/* global __INVITE_KEY__ */
/* global __LATEST_TERMS__ */
/* global __PASSWORD_MIN_LENGTH__ */
/* global __PASSWORD_MAX_LENGTH__ */
/* global __ABOUT_MAX_LENGTH__ */
/* global __I18N_ENABLED__ */
/* global __RX_ENABLED__ */
/* global __CLINICS_ENABLED__ */
/* global __VERSION__ */

function booleanFromText(value, defaultValue) {
  if (value === 'true') {
    return true;
  }

  if (value === 'false') {
    return false;
  }

  return (typeof value === 'boolean') ? value : defaultValue || false;
}

function integerFromText(value, defaultValue) {
  value = parseInt(value, 10);
  if (isNaN(value)) {
    return defaultValue === undefined ? 0 : defaultValue;
  }
  return value;
}

const config = {
  VERSION: __VERSION__,
  UPLOAD_API: __UPLOAD_API__ || 'https://tidepool.org/uploader',
  API_HOST: __API_HOST__ || `${window.location.protocol}//${window.location.host}`,
  INVITE_KEY: __INVITE_KEY__ || '',
  LATEST_TERMS: __LATEST_TERMS__ || null,
  PASSWORD_MIN_LENGTH: integerFromText(__PASSWORD_MIN_LENGTH__, 8),
  PASSWORD_MAX_LENGTH: integerFromText(__PASSWORD_MAX_LENGTH__, 72),
  ABOUT_MAX_LENGTH: integerFromText(__ABOUT_MAX_LENGTH__, 256),
  I18N_ENABLED: booleanFromText(__I18N_ENABLED__, false),
  RX_ENABLED: booleanFromText(__RX_ENABLED__, false),
  CLINICS_ENABLED: booleanFromText(__CLINICS_ENABLED__, false),
}

if (__DEV__) {
  window.config = config;
}

// the constants below are defined in webpack.config.js -- they're aliases for
// environment variables.
module.exports = config;
<|endoftext|>


<commit_before>//create new game with a size of 400px by 490px
var game = new Phaser.Game(400, 490, Phaser.AUTO, 'gameDiv');

var scoreDebounce = false;
var score = 0;

var started = false;
var spaceKey;

//the menu state of the game: this is the main menu
var menuState = {

    preload: function() {
        game.stage.backgroundColor = '#FFFFFF';

        game.load.image('playButton', '../res/images/playButton.png');
    },

    create: function() {
        this.titleText = game.add.text(200, 150, 'Super Duck Flap', { font: '48px Arial', fill: '#000' });
        this.titleText.anchor.setTo(0.5, 0.5);
        this.titleText.angle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);
        this.titleTween = game.add.tween(this.titleText);
        this.titleTween.to({ angle: -this.titleText.angle }, 5000 + Math.random() * 5000, Phaser.Easing.Linear.None, true, 0, 1000, true);

        this.playButton = game.add.button(200, 350, 'playButton', this.onPlayPressed, this);
        this.playButton.anchor.setTo(0.5, 0.5);
    },

    onPlayPressed: function() {
        game.state.start('preMain');
    }

};

//the pre-main state of the game: this is where the user will be prompted to flap to start the game
var preMainState = {

    preload: function() {
        game.stage.backgroundColor = '#FFFFFF';

        game.load.image('duck', 'res/images/bird.png');
    },

    create: function() {
        this.startText = game.add.text(150, 200, 'Flap to Start!', { font: '30px Arial', fill: '#000000' });
        this.startText.anchor.setTo(0.5, 0.5);

        spaceKey = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
        spaceKey.onDown.add(this.start, this);

        game.input.onDown.add(this.start, this);

        this.bird = game.add.sprite(100, 100, 'duck');
        this.bird.anchor.setTo(-0.2, 0.5);
    },

    start: function() {
        started = true;

        game.state.start('main');
    }

}

//the main state of the game: this is where all of the playing happens
var mainState = {

    preload: function() {
        game.stage.backgroundColor = '#FFFFFF';

        game.load.image('duck', 'res/images/bird.png');
        game.load.spritesheet('bird', 'res/images/birdSheet.png', 64, 36);

        //game.load.image('pipe', 'res/images/pipe.png');
        game.load.spritesheet('pipe', 'res/images/pipeSheet.png', 60, 60);

        game.load.audio('jump', 'res/sounds/jump.wav');
    },

    create: function() {
        game.physics.startSystem(Phaser.Physics.ARCADE);

        this.bird = game.add.sprite(100, 100, 'bird');

        this.bird.animations.add('flap');

        game.physics.arcade.enable(this.bird);
        this.bird.body.gravity.y = 1000;

        spaceKey = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
        spaceKey.onDown.add(this.jump, this);
        spaceKey.onUp.add(this.unJump, this);

        game.input.onDown.add(this.jump, this);
        game.input.onUp.add(this.unJump, this);

        this.pipes = game.add.group();
        this.pipes.enableBody = true;
        //this.pipes.createMultiple(20, 'pipe');

        this.timer = game.time.events.loop(1500, this.addRowOfPipes, this);

        score = 0;
        this.labelScore = game.add.text(20, 20, '0', { font: '30px Arial', fill: '#000000' });

        this.bird.anchor.setTo(-0.2, 0.5);

        this.jumpSound = game.add.audio('jump');

        this.bird.alive = true;

        if (started) {
            started = false;

            this.jump();
            this.unJump();
        }
    },

    update: function() {
        if (this.bird.inWorld == false)
            this.restartGame();

        game.physics.arcade.overlap(this.bird, this.pipes, this.hitPipe, null, this);

        if (this.bird.alive) {
            this.bird.anchor.setTo(-0.2, 0.5);
        }

        if (this.bird.angle < 20 && this.bird.alive) this.bird.angle += 1;

        this.pipes.remove(this.pipes.getFirstDead());
    },

    jump: function() {
        if (!this.bird.alive)
            return;

        if (this.touched)
            return;

        this.bird.body.velocity.y = -400; //default: -350

        game.add.tween(this.bird).to({ angle: -20 }, 100).start();

        this.jumpSound.play();

        this.touched = true;

        this.bird.animations.play('flap', 15, false);
    },

    unJump: function() {
        this.touched = false;
    },

    restartGame: function() {
        this.scoreDebounce = false;

        game.state.start('gameOver');
    },

    addOnePipe: function(x, y, pos) {
        var pipe = this.pipes.create(x, y, 'pipe');
        pipe.animations.add('mid', [0], 0);
        pipe.animations.add('endDown', [1], 0);
        pipe.animations.add('endUp', [2], 0)

        pipe.reset(x, y);

        pipe.body.velocity.x = -300; //default: -200

        pipe.checkWorldBounds = true;
        pipe.outOfBoundsKill = true;

        switch (pos) {
            case -1:
                pipe.animations.play('endUp');
                break;

            case 0:
                pipe.animations.play('mid');
                break;

            case 1:
                pipe.animations.play('endDown');
                break;
        }
    },

    addRowOfPipes: function() {
        var hole = Math.floor(Math.random() * 5) + 1;

        for (var i = 0; i < 8; i++) {
            if (i != hole && i != hole + 1) {
                var pos = 0;
                if (i + 1 == hole) pos = 1;
                else if (i - 2 == hole) pos = -1;

                this.addOnePipe(400, i * 60 + 5, pos);
            }
        }

        if (this.scoreDebounce == true) {
            score += 1;
        }

        this.scoreDebounce = true;

        this.labelScore.text = score;
    },

    hitPipe: function() {
        if (this.bird.alive == false)
            return;

        this.bird.alive = false;

        game.time.events.remove(this.timer);

        this.pipes.forEachAlive(function(p) {
            p.body.velocity.x = 0;
        }, this);

        this.scoreDebounce = false;

        this.bird.body.gravity.y = 1600;

        game.add.tween(this.bird).to({ angle: 90 }, 1000).start();
    }

};

var gameOverState = {

    preload: function() {
        game.stage.backgroundColor = '#FFFFFF';

        game.load.image('retryButton', '../res/images/retryButton.png');
        game.load.image('exitButton', '../res/images/exitButton.png');
    },

    create: function() {
        //this.newAngle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);

        this.duckEmitter = game.add.emitter(game.world.centerX, 0, 0);
        this.duckEmitter.makeParticles('duck');
        this.duckEmitter.minParticleSpeed.set(0, 300);
        this.duckEmitter.maxParticleSpeed.set(0, 400);
        this.duckEmitter.gravity = 1000;
        this.duckEmitter.width = 400;
        this.duckEmitter.setRotation(1000, 1000);
        this.duckEmitter.setAlpha(0.8, 0.8);
        this.duckEmitter.setScale(0.6, 0.8);
        this.duckEmitter.start(false, 5000, 1000);

        this.gameOverText = game.add.text(200, 100, 'Game Over!', { font: '48px Arial', fill: '#000' });
        this.gameOverText.anchor.setTo(0.5, 0.5);
        //this.gameOverText.angle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);
        //this.gameOverTween = game.add.tween(this.gameOverText);
        //this.gameOverTween.to({ angle: -this.scoreText.angle }, 5000 + Math.random() * 5000, Phaser.Easing.Linear.None, true, 0, 1000, true);

        this.scoreText = game.add.text(200, 150, '0 Pipes Cleared!', { font: '48px Arial', fill: '#000' });
        this.scoreText.anchor.setTo(0.5, 0.5);
        this.scoreText.text = score + ' Pipes Cleared!';
        //this.scoreText.angle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);;
        //this.scoreTween = game.add.tween(this.scoreText);
        //this.scoreTween.to({ angle: -this.scoreText.angle }, 5000 + Math.random() * 5000, Phaser.Easing.Linear.None, true, 0, 1000, true);

        this.playButton = game.add.button(200, 250, 'retryButton', this.onPlayPressed, this);
        this.playButton.anchor.setTo(0.5, 0.5);

        this.exitButton = game.add.button(200, 350, 'exitButton', this.onExitPressed, this);
        this.exitButton.anchor.setTo(0.5, 0.5);
    },

    onPlayPressed: function() {
        score = 0;

        game.state.start('preMain');
    },

    onExitPressed: function() {
        game.state.start('menu');
    }

};

game.state.add('menu', menuState);
game.state.add('preMain', preMainState);
game.state.add('main', mainState);
game.state.add('gameOver', gameOverState);
game.state.start('menu');<commit_msg>game states<commit_after>var game = new Phaser.Game(400, 490, Phaser.AUTO, 'gameDiv');

var scoreDebounce = false;
var score = 0;

var started = false;
var spaceKey;

var bootState = {

	preload: function() {
		game.stage.backgroundColor = '#212121';
	},

	create: function() {
		game.physics.startSystem(Phaser.Physics.ARCADE);

		game.state.start('load');
	},

	update: function() { }

};

var loadState = {

	preload: function() {
		game.stage.backgroundColor = '#212121';

		game.load.image('playButton', '../res/images/playButton.png');
		game.load.image('retryButton', '../res/images/retryButton.png');
		game.load.image('exitButton', '../res/images/exitButton.png');
		game.load.image('duck', 'res/images/bird.png');

		game.load.spritesheet('bird', 'res/images/birdSheet.png', 64, 36);
		game.load.spritesheet('pipe', 'res/images/pipeSheet.png', 60, 60);

		game.load.audio('jump', 'res/sounds/jump.wav');
	},

	create: function() {
		game.state.start('menu');
	},

	update: function() { }

};

var menuState = {

	preload: function() {
		game.stage.backgroundColor = '#FFFFFF';
	},

	create: function() {
		this.titleText = game.add.text(200, 150, 'Super Duck Flap', { font: '48px Arial', fill: '#000' });
		this.titleText.anchor.setTo(0.5, 0.5);
		this.titleText.angle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);
		this.titleTween = game.add.tween(this.titleText);
		this.titleTween.to({ angle: -this.titleText.angle }, 5000 + Math.random() * 5000, Phaser.Easing.Linear.None, true, 0, 1000, true);

		this.playButton = game.add.button(200, 350, 'playButton', this.onPlayPressed, this);
		this.playButton.anchor.setTo(0.5, 0.5);
	},

	onPlayPressed: function() {
		game.state.start('main1');
	}

};

var main1State = {

	preload: function() {
		game.stage.backgroundColor = '#FFFFFF';
	},

	create: function() {
		this.startText = game.add.text(150, 200, 'Flap to Start!', { font: '30px Arial', fill: '#000000' });
		this.startText.anchor.setTo(0.5, 0.5);

		spaceKey = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
		spaceKey.onDown.add(this.start, this);

		game.input.onDown.add(this.start, this);

		this.bird = game.add.sprite(100, 100, 'duck');
		this.bird.anchor.setTo(-0.2, 0.5);
	},

	start: function() {
		started = true;

		game.state.start('main2');
	}

}

var main2State = {

	preload: function() {
		game.stage.backgroundColor = '#FFFFFF';
	},

	create: function() {
		this.bird = game.add.sprite(100, 100, 'bird');

		this.bird.animations.add('flap');

		game.physics.arcade.enable(this.bird);
		this.bird.body.gravity.y = 1000;

		spaceKey = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
		spaceKey.onDown.add(this.jump, this);
		spaceKey.onUp.add(this.unJump, this);

		game.input.onDown.add(this.jump, this);
		game.input.onUp.add(this.unJump, this);

		this.pipes = game.add.group();
		this.pipes.enableBody = true;

		this.timer = game.time.events.loop(1500, this.addRowOfPipes, this);

		score = 0;
		this.labelScore = game.add.text(20, 20, '0', { font: '30px Arial', fill: '#000000' });

		this.bird.anchor.setTo(-0.2, 0.5);

		this.jumpSound = game.add.audio('jump');

		this.bird.alive = true;

		if (started) {
			started = false;

			this.jump();
			this.unJump();
		}
	},

	update: function() {
		if (this.bird.inWorld == false)
			this.restartGame();

		game.physics.arcade.overlap(this.bird, this.pipes, this.hitPipe, null, this);

		if (this.bird.alive) {
			this.bird.anchor.setTo(-0.2, 0.5);
		}

		if (this.bird.angle < 20 && this.bird.alive) this.bird.angle += 1;

		this.pipes.remove(this.pipes.getFirstDead());
	},

	jump: function() {
		if (!this.bird.alive)
			return;

		if (this.touched)
			return;

		this.bird.body.velocity.y = -400; //default: -350

		game.add.tween(this.bird).to({ angle: -20 }, 100).start();

		this.jumpSound.play();

		this.touched = true;

		this.bird.animations.play('flap', 15, false);
	},

	unJump: function() {
		this.touched = false;
	},

	restartGame: function() {
		this.scoreDebounce = false;

		game.state.start('gameOver');
	},

	addOnePipe: function(x, y, pos) {
		var pipe = this.pipes.create(x, y, 'pipe');
		pipe.animations.add('mid', [0], 0);
		pipe.animations.add('endDown', [1], 0);
		pipe.animations.add('endUp', [2], 0)

		pipe.reset(x, y);

		pipe.body.velocity.x = -300; //default: -200

		pipe.checkWorldBounds = true;
		pipe.outOfBoundsKill = true;

		switch (pos) {
			case -1:
				pipe.animations.play('endUp');
				break;

			case 0:
				pipe.animations.play('mid');
				break;

			case 1:
				pipe.animations.play('endDown');
				break;
		}
	},

	addRowOfPipes: function() {
		var hole = Math.floor(Math.random() * 5) + 1;

		for (var i = 0; i < 8; i++) {
			if (i != hole && i != hole + 1) {
				var pos = 0;
				if (i + 1 == hole) pos = 1;
				else if (i - 2 == hole) pos = -1;

				this.addOnePipe(400, i * 60 + 5, pos);
			}
		}

		if (this.scoreDebounce == true) {
			score += 1;
		}

		this.scoreDebounce = true;

		this.labelScore.text = score;
	},

	hitPipe: function() {
		if (this.bird.alive == false)
			return;

		this.bird.alive = false;

		game.time.events.remove(this.timer);

		this.pipes.forEachAlive(function(p) {
			p.body.velocity.x = 0;
		}, this);

		this.scoreDebounce = false;;

		this.bird.body.gravity.y = 1600;

		game.add.tween(this.bird).to({ angle: 90 }, 1000).start();
	}

};

var gameOverState = {

	preload: function() {
		game.stage.backgroundColor = '#FFFFFF';
	},

	create: function() {
		//this.newAngle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);

		this.duckEmitter = game.add.emitter(game.world.centerX, 0, 0);
		this.duckEmitter.makeParticles('duck');
		this.duckEmitter.minParticleSpeed.set(0, 300);
		this.duckEmitter.maxParticleSpeed.set(0, 400);
		this.duckEmitter.gravity = 1000;
		this.duckEmitter.width = 400;
		this.duckEmitter.setRotation(1000, 1000);
		this.duckEmitter.setAlpha(0.8, 0.8);
		this.duckEmitter.setScale(0.6, 0.8);
		this.duckEmitter.start(false, 5000, 1000);

		this.gameOverText = game.add.text(200, 100, 'Game Over!', { font: '48px Arial', fill: '#000' });
		this.gameOverText.anchor.setTo(0.5, 0.5);
		//this.gameOverText.angle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);
		//this.gameOverTween = game.add.tween(this.gameOverText);
		//this.gameOverTween.to({ angle: -this.scoreText.angle }, 5000 + Math.random() * 5000, Phaser.Easing.Linear.None, true, 0, 1000, true);

		this.scoreText = game.add.text(200, 150, '0 Pipes Cleared!', { font: '48px Arial', fill: '#000' });
		this.scoreText.anchor.setTo(0.5, 0.5);
		this.scoreText.text = score + ' Pipes Cleared!';
		//this.scoreText.angle = (2 + Math.random() * 5) * (Math.random() > 0.5 ? 1 : -1);
		//this.scoreTween = game.add.tween(this.scoreText);
		//this.scoreTween.to({ angle: -this.scoreText.angle }, 5000 + Math.random() * 5000, Phaser.Easing.Linear.None, true, 0, 1000, true);

		this.playButton = game.add.button(200, 250, 'retryButton', this.onPlayPressed, this);
		this.playButton.anchor.setTo(0.5, 0.5);

		this.exitButton = game.add.button(200, 350, 'exitButton', this.onExitPressed, this);
		this.exitButton.anchor.setTo(0.5, 0.5);
	},

	onPlayPressed: function() {
		score = 0;

		game.state.start('main1');
	},

	onExitPressed: function() {
		game.state.start('menu');
	}

};

game.state.add('boot', bootState);
game.state.add('load', loadState);
game.state.add('menu', menuState);
game.state.add('main1', main1State);
game.state.add('main2', main2State);
game.state.add('gameOver', gameOverState);
game.state.start('boot');<|endoftext|>


<commit_before>var object = require(env.Z_PATH_UTILS + 'object');
var EventEmitter = require('events').EventEmitter;

exports.query = function (request) {

    var self = this;

    return function(data, callback) {
        console.log(data);
        self._db.exec(request.query, data).then(function (res){
            callback(null, res);
        }, callback);
    };
}

exports.model = function (db, config) {

    // create new model instance
    var model = new EventEmitter();

    // merge dbModel into model
    for (var prop in config) {
        model[prop] = config[prop];
    }

    // TODO Deprecated?
    // model.collection = db.class.get(config.entity)
    model._db = db;

    return model;
};

<commit_msg>Removed object utils dependency and console.log<commit_after>var EventEmitter = require('events').EventEmitter;

exports.query = function (request) {

    var self = this;

    return function(data, callback) {
        self._db.exec(request.query, data).then(function (res){
            callback(null, res);
        }, callback);
    };
}

exports.model = function (db, config) {

    // create new model instance
    var model = new EventEmitter();

    // merge dbModel into model
    for (var prop in config) {
        model[prop] = config[prop];
    }

    // TODO Deprecated?
    // model.collection = db.class.get(config.entity)
    model._db = db;

    return model;
};
<|endoftext|>


<commit_before>import * as THREE from 'three';
import { RoundedBoxBufferGeometry } from 'three/examples/jsm/geometries/RoundedBoxBufferGeometry.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import Stats from 'stats.js';
import { GUI } from 'dat.gui';
import { MeshBVH, MeshBVHVisualizer } from '../src/index.js';

const params = {

	firstPerson: true,

	displayCollider: false,
	displayBVH: false,
	visualizeDepth: 10,
	gravity: - 9.8,
	playerSpeed: 10,
	physicsSteps: 5,
	reset: reset,

};

let renderer, camera, scene, clock, gui, stats;
let environment, collider, visualizer, player, controls;
let playerVelocity = 0;
let fwdPressed = false, bkdPressed = false, lftPressed = false, rgtPressed = false;
let upVector = new THREE.Vector3( 0, 1, 0 );
let tempVector = new THREE.Vector3();

init();
render();

function init() {

	const bgColor = 0x263238 / 2;

	// renderer setup
	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setClearColor( bgColor, 1 );
	renderer.shadowMap.enabled = true;
	renderer.shadowMap.type = THREE.PCFSoftShadowMap;
	renderer.gammaOutput = true;
	document.body.appendChild( renderer.domElement );

	// scene setup
	scene = new THREE.Scene();
	scene.fog = new THREE.Fog( bgColor, 20, 70 );

	// lights
	const light = new THREE.DirectionalLight( 0xffffff, 1 );
	light.position.set( 1, 1.5, 1 ).multiplyScalar( 50 );
	light.shadow.mapSize.setScalar( 2048 );
	light.shadow.bias = - 1e-4;
	light.shadow.normalBias = 0.1;
	light.castShadow = true;

	const shadowCam = light.shadow.camera;
	shadowCam.bottom = shadowCam.left = - 30;
	shadowCam.top = 30;
	shadowCam.right = 45;

	scene.add( light );
	scene.add( new THREE.HemisphereLight( 0xffffff, 0x223344, 0.4 ) );

	// camera setup
	camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
	camera.position.set( 10, 10, - 10 );
	camera.far = 100;
	camera.updateProjectionMatrix();
	window.camera = camera;

	clock = new THREE.Clock();

	controls = new OrbitControls( camera, renderer.domElement );

	// stats setup
	stats = new Stats();
	document.body.appendChild( stats.dom );

	loadColliderEnvironment();

	// character
	player = new THREE.Mesh(
		new RoundedBoxBufferGeometry( 1.0, 2.0, 1.0, 10, 0.5 ),
		new THREE.MeshStandardMaterial()
	);
	player.castShadow = true;
	player.receiveShadow = true;
	scene.add( player );
	reset();

	// dat.gui
	gui = new GUI();
	gui.add( params, 'firstPerson' ).onChange( v => {

		// TODO: jump the camera back to a comfortable distance

	} );

	const visFolder = gui.addFolder( 'Visualization' );
	visFolder.add( params, 'displayCollider' );
	visFolder.add( params, 'displayBVH' );
	visFolder.add( params, 'visualizeDepth', 1, 20, 1 ).onChange( v => {

		visualizer.depth = v;
		visualizer.update();

	} );
	visFolder.open();

	const physicsFolder = gui.addFolder( 'Player' );
	physicsFolder.add( params, 'physicsSteps', 0, 30, 1 );
	physicsFolder.add( params, 'gravity', - 100, 100, 0.01 ).onChange( v => {

		params.gravity = parseFloat( v );

	} );
	physicsFolder.add( params, 'playerSpeed', 1, 20 );
	physicsFolder.open();

	gui.add( params, 'reset' );
	gui.open();

	window.addEventListener( 'resize', function () {

		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();

		renderer.setSize( window.innerWidth, window.innerHeight );

	}, false );

	window.addEventListener( 'keydown', function ( e ) {

		switch ( e.code ) {

			case 'KeyW': fwdPressed = true; break;
			case 'KeyS': bkdPressed = true; break;
			case 'KeyD': rgtPressed = true; break;
			case 'KeyA': lftPressed = true; break;

		}

	} );

	window.addEventListener( 'keyup', function ( e ) {

		switch ( e.code ) {

			case 'KeyW': fwdPressed = false; break;
			case 'KeyS': bkdPressed = false; break;
			case 'KeyD': rgtPressed = false; break;
			case 'KeyA': lftPressed = false; break;

		}

	} );

}

function loadColliderEnvironment() {

	new GLTFLoader().load( '../models/dungeon_low_poly_game_level_challenge/scene.gltf', res => {

		const gltfScene = res.scene;
		gltfScene.scale.setScalar( .01 );

		const box = new THREE.Box3();
		box.setFromObject( gltfScene );
		box.getCenter( gltfScene.position ).negate();
		gltfScene.updateMatrixWorld( true );

		// visual geometry setup
		const toMerge = {};
		gltfScene.traverse( c => {

			if (
				/Boss/.test( c.name ) ||
				/Enemie/.test( c.name ) ||
				/Shield/.test( c.name ) ||
				/Sword/.test( c.name ) ||
				/Character/.test( c.name ) ||
				/Gate/.test( c.name ) ||

				// spears
				/Cube/.test( c.name ) ||

				// pink brick
				c.material && c.material.color.r === 1.0
			) {

				return;

			}

			if ( c.isMesh ) {

				const hex = c.material.color.getHex();
				toMerge[ hex ] = toMerge[ hex ] || [];
				toMerge[ hex ].push( c );

			}

		} );

		environment = new THREE.Group();
		for ( const hex in toMerge ) {

			const arr = toMerge[ hex ];
			const visualGeometries = [];
			arr.forEach( mesh => {

				if ( mesh.material.emissive.r !== 0 ) {

					environment.attach( mesh );

				} else {

					const geom = mesh.geometry.clone();
					geom.applyMatrix4( mesh.matrixWorld );
					visualGeometries.push( geom );

				}

			} );

			if ( visualGeometries.length ) {

				const newGeom = BufferGeometryUtils.mergeBufferGeometries( visualGeometries );
				const newMesh = new THREE.Mesh( newGeom, new THREE.MeshStandardMaterial( { color: parseInt( hex ) } ) );
				newMesh.castShadow = true;
				newMesh.receiveShadow = true;
				environment.add( newMesh );

			}

		}

		// collect all geometries to merge
		const geometries = [];
		environment.updateMatrixWorld( true );
		environment.traverse( c => {

			if ( c.geometry ) {

				const cloned = c.geometry.clone();
				cloned.applyMatrix4( c.matrixWorld );
				for ( const key in cloned.attributes ) {

					if ( key !== 'position' ) {

						cloned.deleteAttribute( key );

					}

				}
				geometries.push( cloned );

			}

		} );

		// create the merged geometry
		const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries, false );
		mergedGeometry.boundsTree = new MeshBVH( mergedGeometry, { lazyGeneration: false } );

		collider = new THREE.Mesh( mergedGeometry );
		collider.material.wireframe = true;
		collider.material.opacity = 0.5;
		collider.material.transparent = true;

		visualizer = new MeshBVHVisualizer( collider, params.visualizeDepth );
		scene.add( visualizer );
		scene.add( collider );
		scene.add( environment );

	} );

}

function reset() {

	player.position.set( 15.75, - 3, 30 );
	camera.position.sub( controls.target );
	controls.target.copy( player.position );
	camera.position.add( player.position );
	controls.update();

}

function updatePlayer( delta ) {

	playerVelocity += delta * params.gravity;
	// player.position.y += playerVelocity;

	window.controls = controls;

	// move the player
	const angle = controls.getAzimuthalAngle();
	if ( fwdPressed ) {

		tempVector.set( 0, 0, - 1 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	if ( bkdPressed ) {

		tempVector.set( 0, 0, 1 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	if ( lftPressed ) {

		tempVector.set( - 1, 0, 0 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	if ( rgtPressed ) {

		tempVector.set( 1, 0, 0 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	// TODO: adjust player position based on collisions
	// TODO: detect if player is on ground and enable jump

	// adjust the camera
	camera.position.sub( controls.target );
	controls.target.copy( player.position );
	camera.position.add( player.position );
	controls.update();

}

function render() {

	stats.update();
	requestAnimationFrame( render );

	const delta = Math.min( clock.getDelta(), 0.1 );
	if ( params.firstPerson ) {

		controls.maxPolarAngle = Math.PI;
		controls.minDistance = 1e-4;
		controls.maxDistance = 1e-4;

	} else {

		controls.maxPolarAngle = Math.PI / 2;
		controls.minDistance = 1;
		controls.maxDistance = 20;

	}

	if ( collider ) {

		collider.visible = params.displayCollider;
		visualizer.visible = params.displayBVH;
		updatePlayer( delta );

	}

	renderer.render( scene, camera );

}
<commit_msg>Update character movement<commit_after>import * as THREE from 'three';
import { RoundedBoxBufferGeometry } from 'three/examples/jsm/geometries/RoundedBoxBufferGeometry.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import Stats from 'stats.js';
import { GUI } from 'dat.gui';
import { MeshBVH, MeshBVHVisualizer } from '../src/index.js';

const params = {

	firstPerson: false,

	displayCollider: false,
	displayBVH: false,
	visualizeDepth: 10,
	gravity: - 2,
	playerSpeed: 10,
	physicsSteps: 5,

	reset: reset,

};

let renderer, camera, scene, clock, gui, stats;
let environment, collider, visualizer, player, controls;
let playerVelocity = 0;
let fwdPressed = false, bkdPressed = false, lftPressed = false, rgtPressed = false;
let upVector = new THREE.Vector3( 0, 1, 0 );
let tempVector = new THREE.Vector3();
let tempVector2 = new THREE.Vector3();
let tempVector3 = new THREE.Vector3();
let tempBox = new THREE.Box3();
let tempMat = new THREE.Matrix4();
let tempSegment = new THREE.Line3();

init();
render();

function init() {

	const bgColor = 0x263238 / 2;

	// renderer setup
	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setClearColor( bgColor, 1 );
	renderer.shadowMap.enabled = true;
	renderer.shadowMap.type = THREE.PCFSoftShadowMap;
	renderer.gammaOutput = true;
	document.body.appendChild( renderer.domElement );

	// scene setup
	scene = new THREE.Scene();
	scene.fog = new THREE.Fog( bgColor, 20, 70 );

	// lights
	const light = new THREE.DirectionalLight( 0xffffff, 1 );
	light.position.set( 1, 1.5, 1 ).multiplyScalar( 50 );
	light.shadow.mapSize.setScalar( 2048 );
	light.shadow.bias = - 1e-4;
	light.shadow.normalBias = 0.1;
	light.castShadow = true;

	const shadowCam = light.shadow.camera;
	shadowCam.bottom = shadowCam.left = - 30;
	shadowCam.top = 30;
	shadowCam.right = 45;

	scene.add( light );
	scene.add( new THREE.HemisphereLight( 0xffffff, 0x223344, 0.4 ) );

	// camera setup
	camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
	camera.position.set( 10, 10, - 10 );
	camera.far = 100;
	camera.updateProjectionMatrix();
	window.camera = camera;

	clock = new THREE.Clock();

	controls = new OrbitControls( camera, renderer.domElement );

	// stats setup
	stats = new Stats();
	document.body.appendChild( stats.dom );

	loadColliderEnvironment();

	// character
	player = new THREE.Mesh(
		new RoundedBoxBufferGeometry( 1.0, 2.0, 1.0, 10, 0.5 ),
		new THREE.MeshStandardMaterial()
	);
	player.geometry.translate( 0, - 0.5, 0 );
	player.capsuleInfo = {
		radius: 0.5,
		segment: new THREE.Line3( new THREE.Vector3(), new THREE.Vector3( 0, - 1.0, 0.0 ) )
	};
	player.castShadow = true;
	player.receiveShadow = true;
	scene.add( player );
	reset();

	// dat.gui
	gui = new GUI();
	gui.add( params, 'firstPerson' ).onChange( v => {

		// TODO: jump the camera back to a comfortable distance

	} );

	const visFolder = gui.addFolder( 'Visualization' );
	visFolder.add( params, 'displayCollider' );
	visFolder.add( params, 'displayBVH' );
	visFolder.add( params, 'visualizeDepth', 1, 20, 1 ).onChange( v => {

		visualizer.depth = v;
		visualizer.update();

	} );
	visFolder.open();

	const physicsFolder = gui.addFolder( 'Player' );
	physicsFolder.add( params, 'physicsSteps', 0, 30, 1 );
	physicsFolder.add( params, 'gravity', - 100, 100, 0.01 ).onChange( v => {

		params.gravity = parseFloat( v );

	} );
	physicsFolder.add( params, 'playerSpeed', 1, 20 );
	physicsFolder.open();

	gui.add( params, 'reset' );
	gui.open();

	window.addEventListener( 'resize', function () {

		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();

		renderer.setSize( window.innerWidth, window.innerHeight );

	}, false );

	window.addEventListener( 'keydown', function ( e ) {

		switch ( e.code ) {

			case 'KeyW': fwdPressed = true; break;
			case 'KeyS': bkdPressed = true; break;
			case 'KeyD': rgtPressed = true; break;
			case 'KeyA': lftPressed = true; break;
			case 'Space': playerVelocity = 0.3; break; // TODO: only jump if we're on the ground

		}

	} );

	window.addEventListener( 'keyup', function ( e ) {

		switch ( e.code ) {

			case 'KeyW': fwdPressed = false; break;
			case 'KeyS': bkdPressed = false; break;
			case 'KeyD': rgtPressed = false; break;
			case 'KeyA': lftPressed = false; break;

		}

	} );

}

function loadColliderEnvironment() {

	new GLTFLoader().load( '../models/dungeon_low_poly_game_level_challenge/scene.gltf', res => {

		const gltfScene = res.scene;
		gltfScene.scale.setScalar( .01 );

		const box = new THREE.Box3();
		box.setFromObject( gltfScene );
		box.getCenter( gltfScene.position ).negate();
		gltfScene.updateMatrixWorld( true );

		// visual geometry setup
		const toMerge = {};
		gltfScene.traverse( c => {

			if (
				/Boss/.test( c.name ) ||
				/Enemie/.test( c.name ) ||
				/Shield/.test( c.name ) ||
				/Sword/.test( c.name ) ||
				/Character/.test( c.name ) ||
				/Gate/.test( c.name ) ||

				// spears
				/Cube/.test( c.name ) ||

				// pink brick
				c.material && c.material.color.r === 1.0
			) {

				return;

			}

			if ( c.isMesh ) {

				const hex = c.material.color.getHex();
				toMerge[ hex ] = toMerge[ hex ] || [];
				toMerge[ hex ].push( c );

			}

		} );

		environment = new THREE.Group();
		for ( const hex in toMerge ) {

			const arr = toMerge[ hex ];
			const visualGeometries = [];
			arr.forEach( mesh => {

				if ( mesh.material.emissive.r !== 0 ) {

					environment.attach( mesh );

				} else {

					const geom = mesh.geometry.clone();
					geom.applyMatrix4( mesh.matrixWorld );
					visualGeometries.push( geom );

				}

			} );

			if ( visualGeometries.length ) {

				const newGeom = BufferGeometryUtils.mergeBufferGeometries( visualGeometries );
				const newMesh = new THREE.Mesh( newGeom, new THREE.MeshStandardMaterial( { color: parseInt( hex ) } ) );
				newMesh.castShadow = true;
				newMesh.receiveShadow = true;
				environment.add( newMesh );

			}

		}

		// collect all geometries to merge
		const geometries = [];
		environment.updateMatrixWorld( true );
		environment.traverse( c => {

			if ( c.geometry ) {

				const cloned = c.geometry.clone();
				cloned.applyMatrix4( c.matrixWorld );
				for ( const key in cloned.attributes ) {

					if ( key !== 'position' ) {

						cloned.deleteAttribute( key );

					}

				}
				geometries.push( cloned );

			}

		} );

		// create the merged geometry
		const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries, false );
		mergedGeometry.boundsTree = new MeshBVH( mergedGeometry, { lazyGeneration: false } );

		collider = new THREE.Mesh( mergedGeometry );
		collider.material.wireframe = true;
		collider.material.opacity = 0.5;
		collider.material.transparent = true;

		visualizer = new MeshBVHVisualizer( collider, params.visualizeDepth );
		scene.add( visualizer );
		scene.add( collider );
		scene.add( environment );

	} );

}

function reset() {

	playerVelocity = 0;
	player.position.set( 15.75, - 3, 30 );
	camera.position.sub( controls.target );
	controls.target.copy( player.position );
	camera.position.add( player.position );
	controls.update();

}

function updatePlayer( delta ) {

	playerVelocity += delta * params.gravity;
	player.position.y += playerVelocity;

	// move the player
	const angle = controls.getAzimuthalAngle();
	if ( fwdPressed ) {

		tempVector.set( 0, 0, - 1 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	if ( bkdPressed ) {

		tempVector.set( 0, 0, 1 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	if ( lftPressed ) {

		tempVector.set( - 1, 0, 0 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	if ( rgtPressed ) {

		tempVector.set( 1, 0, 0 ).applyAxisAngle( upVector, angle );
		player.position.addScaledVector( tempVector, params.playerSpeed * delta );

	}

	player.updateMatrixWorld();

	// TODO: adjust player position based on collisions
	const capsuleInfo = player.capsuleInfo;
	tempBox.makeEmpty();
	tempMat.copy( collider.matrixWorld ).invert();
	tempSegment.copy( capsuleInfo.segment );

	tempSegment.start.applyMatrix4( player.matrixWorld ).applyMatrix4( tempMat );
	tempBox.expandByPoint( tempSegment.start );

	tempSegment.end.applyMatrix4( player.matrixWorld ).applyMatrix4( tempMat );
	tempBox.expandByPoint( tempSegment.end );

	tempBox.min.addScalar( - capsuleInfo.radius );
	tempBox.max.addScalar( capsuleInfo.radius );

	const averageNormal = tempVector3;
	let triangleCount = 0;
	collider.geometry.boundsTree.shapecast(
		collider,
		box => box.intersectsBox( tempBox ),
		tri => {

			const triPoint = tempVector;
			const capsulePoint = tempVector2;

			const distance = tri.closestPointToSegment( tempSegment, triPoint, capsulePoint );
			if ( distance < capsuleInfo.radius ) {

				const depth = capsuleInfo.radius - distance;
				const direction = capsulePoint.sub( triPoint ).normalize();

				tempSegment.start.addScaledVector( direction, depth );
				tempSegment.end.addScaledVector( direction, depth );

				averageNormal.add( direction );
				triangleCount ++;

			}

		}
	);

	// TODO: we need to adjust the velocity based on whether the user's on the ground
	// TODO: we should not slide down stairs
	player.position.copy( tempSegment.start ).applyMatrix4( collider.matrixWorld );

	if ( triangleCount !== 0 ) {

		playerVelocity = 0;

	}

	// TODO: detect if player is on ground and enable jump

	// adjust the camera
	camera.position.sub( controls.target );
	controls.target.copy( player.position );
	camera.position.add( player.position );
	controls.update();

	if ( player.position.y < - 25 ) {

		reset();

	}

}

function render() {

	stats.update();
	requestAnimationFrame( render );

	const delta = Math.min( clock.getDelta(), 0.1 );
	if ( params.firstPerson ) {

		controls.maxPolarAngle = Math.PI;
		controls.minDistance = 1e-4;
		controls.maxDistance = 1e-4;

	} else {

		controls.maxPolarAngle = Math.PI / 2;
		controls.minDistance = 1;
		controls.maxDistance = 20;

	}

	if ( collider ) {

		collider.visible = params.displayCollider;
		visualizer.visible = params.displayBVH;

		const physicsSteps = params.physicsSteps;

		for ( let i = 0; i < physicsSteps; i ++ ) {

			updatePlayer( delta / physicsSteps );

		}

	}

	renderer.render( scene, camera );

}
<|endoftext|>