GlobalAPI.js

/*
 * Copyright 2020 WICKLETS LLC
 *
 * This file is part of Wick Engine.
 *
 * Wick Engine is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Wick Engine 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Wick Engine.  If not, see <https://www.gnu.org/licenses/>.
 */

GlobalAPI = class {
    /**
     * Defines all api members such as functions and properties.
     * @type {string[]}
     */
    static get apiMemberNames () {
        return [
            'stop','play','gotoAndStop','gotoAndPlay','gotoNextFrame','gotoPrevFrame',
            // These are currently disabled, they are very slow for some reason.
            // They are currently hacked in inside Tickable._runFunction
            //'project','root','parent','parentObject',
            'isMouseDown','mouseX','mouseY','mouseMoveX','mouseMoveY',
            'key','keys','isKeyDown','keyIsDown','isKeyJustPressed','keyIsJustPressed',
            'random',
            'playSound','stopAllSounds',
            'onEvent',
            'hideCursor','showCursor',
            'hitTestOptions',
        ];
    }

    /**
     * @param {object} scriptOwner The tickable object which owns the script being evaluated.
     */
    constructor (scriptOwner) {
        this.scriptOwner = scriptOwner;
    }

    /**
     * Returns a list of api members bound to the script owner.
     * @returns {object[]} Array of functions, properties, and api members.
     */
    get apiMembers () {
        var members = [];

        GlobalAPI.apiMemberNames.forEach(name => {
            var fn = this[name];
            if(fn instanceof Function) {
                fn = fn.bind(this);
            }
            members.push({
                name: name,
                fn: fn,
            })
        });

        return members;
    }

    /**
     * Stops the timeline of the object's parent clip.
     */
    stop () {
        this.scriptOwner.parentClip.stop();
    }

    /**
     * Plays the timeline of the object's parent clip.
     */
    play () {
        this.scriptOwner.parentClip.play();
    }

    /**
     * Moves the plahead of the parent clip to a frame and stops the timeline of that parent clip.
     * @param {string | number} frame Frame name or number to move playhead to.
     */
    gotoAndStop (frame) {
        this.scriptOwner.parentClip.gotoAndStop(frame);
    }

    /**
     * Moves the plahead of the parent clip to a frame and plays the timeline of that parent clip.
     * @param {string | number} frame Frame name or number to move playhead to.
     */
    gotoAndPlay (frame) {
        this.scriptOwner.parentClip.gotoAndPlay(frame);
    }

    /**
     * Moves the playhead of the parent clip of the object to the next frame.
     */
    gotoNextFrame () {
        this.scriptOwner.parentClip.gotoNextFrame();
    }

    /**
     * Moves the playhead of the parent clip of this object to the previous frame.
     */
    gotoPrevFrame () {
        this.scriptOwner.parentClip.gotoPrevFrame();
    }

    hitTestOptions (options) {
        this.scriptOwner.project.hitTestOptions = options;
    }

    /**
     * Returns an object representing the project with properties such as width, height, framerate, background color, and name.
     * @returns {object} Project object.
     */
    get project () {
        var project = this.scriptOwner.project && this.scriptOwner.project.root;
        if(project) {
            // Attach some aliases to the project settings
            project.width = this.scriptOwner.project.width;
            project.height = this.scriptOwner.project.height;
            project.framerate = this.scriptOwner.project.framerate;
            project.backgroundColor = this.scriptOwner.project.backgroundColor;
            project.name = this.scriptOwner.project.name;
            project.hitTestOptions = this.scriptOwner.project.hitTestOptions;
        }
        return project;
    }

    /**
     * @deprecated
     * Legacy item which returns the project. Use 'project' instead.
     */
    get root () {
        return this.project;
    }

    /**
     * Returns a reference to the current object's parent.
     * @returns Current object's parent.
     */
    get parent () {
        return this.scriptOwner.parentClip;
    }

    /**
     * @deprecated
     * Legacy item which returns the parent clip. Use 'parent' instead.
     */
    get parentObject () {
        return this.scriptOwner.parentClip;
    }

    /**
     * Returns the last key pressed down.
     * @returns {string | null} Returns null if no key has been pressed yet.
     */
    get key () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.currentKey;
    }

    /**
     * Returns a list of all keys currently pressed down.
     * @returns {string[]} All keys represented as strings. If no keys are pressed, an empty array is returned.
     */
    get keys () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.keysDown;
    }

    /**
     * Returns true if the given key is currently down.
     * @param {string} key
     * @returns {bool}
     */
    isKeyDown (key) {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.isKeyDown(key);
    }

    /**
     * @deprecated
     * Legacy item, use 'isKeyDown' instead.
     */
    keyIsDown (key) {
        return this.isKeyDown(key.toLowerCase());
    }

    /**
     * Returns true if the given key was just pressed within the last tick.
     * @param {string} key
     * @returns {bool}
     */
    isKeyJustPressed (key) {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.isKeyJustPressed(key);
    }

    /**
     * @deprecated
     * Legacy item, use 'isKeyJustPressed' instead.
     */
    keyIsJustPressed (key) {
        return this.keyIsJustPressed(key.toLowerCase());
    }

    /**
     * Returns true if the mouse is currently held down.
     * @returns {bool | null} Returns null if the object does not have a project.
     */
    isMouseDown () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.isMouseDown;
    }

    /**
     * Returns the current x position of the mouse in relation to the canvas.
     * @returns {number}
     */
    get mouseX () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.mousePosition.x;
    }

    /**
     * Returns the current y position of the mouse in relation to the canvas.
     * @returns {number}
     */
    get mouseY () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.mousePosition.y;
    }

    /**
     * Returns the amount the mouse moved in the last tick on the x axis.
     * @returns {number}
     */
    get mouseMoveX () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.mouseMove.x;
    }

    /**
     * Returns the amount the mouse moved in the last tick on the y axis.
     * @returns {number}
     */
    get mouseMoveY () {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.mouseMove.y;
    }

    /**
     * Returns a new random object.
     * @returns {GlobalAPI.Random}
     */
    get random () {
        return new GlobalAPI.Random();
    }

    /**
     * Plays a sound which is currently in the asset library.
     * @param {string} name - name of the sound asset in the library.
     * @param {Object} options - options for the sound. See Wick.SoundAsset.play
     * @returns {object} object representing the sound which was played.
     */
    playSound (assetName, options) {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.playSound(assetName, options);
    }

    /**
     * Stops sound(s) currently playing.
     * @param {string} assetName - The name of the SoundAsset to stop.
     * @param {number} id - (optional) The ID of the sound to stop. Returned by playSound. If an ID is not given, all instances of the given sound asset will be stopped.
     */
    stopSound (assetName, id) {
        if(!this.scriptOwner.project) return null;
        return this.scriptOwner.project.stopSound(assetName, id);
    }
    
    /**
     * Stops all currently playing sounds.
     */
    stopAllSounds () {
        if(!this.scriptOwner.project) return null;
        this.scriptOwner.project.stopAllSounds();
    }

    /**
     * Attach a function to an event with a given name.
     * @param {string} name - the name of the event to attach the function to
     * @param {function} fn - the function to attach to the event
     */
    onEvent (name, fn) {
        this.scriptOwner.onEvent(name, fn);
    }

    /**
     * Hide the cursor while the project is running.
     */
    hideCursor () {
        if(!this.scriptOwner.project) return null;
        this.scriptOwner.project.hideCursor = true;
    }

    /**
     * Don't hide the cursor while the project is running.
     */
    showCursor () {
        if(!this.scriptOwner.project) return null;
        this.scriptOwner.project.hideCursor = false;
    }
}

GlobalAPI.Random = class {
    constructor () {

    }

    /**
     * Returns a random integer (whole number) between two given numbers, 0 and a given number, or 0 and 1. The random number is inclusive of the maximum range.
     * @param {number} min The minimum of the returned integer, or the maximum of the returned number if it is the only argument.
     * @param {number} max The maximum of the returned integer.
     * @returns {number} A random number between num1 and num2, 0 and num1, or 0 and 1. Will return 0 if max is greater than min.
     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
     */
    integer(min,max) {

        if (typeof min === 'undefined' && typeof max === 'undefined') {
            min = 0;
            max = 1;
        } else if (typeof min === 'number' && typeof max === 'undefined') {
            max = Math.ceil(min);
            min = 0;
        } 

        if (max < min) return 0;

        // The maximum is inclusive and the minimum is inclusive
        return Math.floor(Math.random() * (max - min + 1) + min); 
    }

    /**
     * Returns a random floating point (decimal) number between two given numbers, 0 and a given number, or 0 and 1.
     * @param {number} num1 The minimum of the returned number, or the maximum of the returned number if it is the only argument.
     * @param {number} num2 The maximum of the returned number.
     * @returns {number} A random number between num1 and num2, 0 and num1, or 0 and 1.
     * https://stackoverflow.com/questions/4959975/generate-random-number-between-two-numbers-in-javascript
     */
    float(num1, num2) {
		if ((typeof num1 !== "undefined") && (typeof num2 !== "undefined")) {
			return (Math.random()*(num2-num1)+num1);
		} else if ((typeof num1 !== "undefined") && (typeof num2 == "undefined")) {
			return Math.random()*num1;
		} else {
			return Math.random();
		}
    }

    /**
     * Returns a random item from an array of items.
     * @param {array} An array of objects.
     * @returns {object | null} A random item contained in the array. Returns null if the given array has no items.
     * https://stackoverflow.com/questions/4550505/getting-a-random-value-from-a-javascript-array
     */
    choice(array) {
        if (array.length <= 0) return null;
        return array[Math.floor(Math.random() * array.length)]
    }
}