Source: scene.js

var Maze = (function (my) {

    /**
     * 3D maze scene. Displays a Game_Map using a raycasting engine.
     * It can both work as a seamless transition from Scene_Map (with map events still running) or as a different scene
     * (with map events frozen until this scene is terminated).
     *
     * Since it's CPU intensive, rendering quality can be tweaked to improve FPS.
     *
     * @constructor
     * @memberOf Maze
     */
    var Scene_Maze = my.Scene_Maze = function () {
        this.initialize.apply(this, arguments);
    };

    Scene_Maze.prototype = Object.create(Scene_Map.prototype);
    Scene_Maze.prototype.constructor = Scene_Maze;

    Scene_Maze.prototype.initialize = function () {
        Scene_Map.prototype.initialize.call(this);
    };

    /**
     * Creates the scene. Sets the rendering quality and, if it's in maze mode, saves the current $gameMap (freezing running events as a side effect).
     */
    Scene_Maze.prototype.create = function () {
        Scene_Map.prototype.create.call(this);
        if ($gameSystem.quality == null) {
            $gameSystem.quality = 3;
        }

        switch ($gameSystem.quality) {
            case 0: // low
                this.scaleFactor = 4;
                break;
            case 1: // medium
                this.scaleFactor = 2;
                break;
            case 2: // high
                this.scaleFactor = 1;
                break;
            case 3: // auto
                this.scaleFactor = 2;
                break;
        }

        this.screenSprite = new Sprite(new Bitmap(Graphics.width, Graphics.height));
        this.addChild(this.screenSprite);


        if (my.isMaze) {
            my.gameMap = $gameMap;
            $gameMap = new Game_Map();
        }
    };

    /**
     * Terminates the scene. If in maze mode, restores the previous $gameMap (unfreezing the events in the process).
     */
    Scene_Maze.prototype.terminate = function () {
        Scene_Map.prototype.terminate.call(this);

        if (my.isMaze) {
            $gameMap = my.gameMap;
        }
    };

    /**
     * On map loaded event handler. Creates the raycasting engine.
     */
    Scene_Maze.prototype.onMapLoaded = function () {
        Scene_Map.prototype.onMapLoaded.call(this);

        my.controller = new my.Maze_Controller(10, 3, 0.05, Math.PI / 64);
        this.raycaster = new my.Maze_Raycaster(this.screenSprite, this.scaleFactor, $gameSystem.quality === 3);

        $gameMap.autoplay();
        this.createWindows();
    };

    /**
     * Creates the spriteset for the scene. Unlike Scene_Map.createSpriteset, it doesn't add the spriteset to the display tree.
     */
    Scene_Maze.prototype.createSpriteset = function () {
        this._spriteset = new Spriteset_Map();

        // Save the tilemap bitmaps for wall rendering.
        my.bmps = this._spriteset._tilemap.bitmaps;
    };

    /**
     * Disables Scene_Map.updateDestination, to disable mouse/touchscreen inputs.
     */
    Scene_Maze.prototype.updateDestination = function () {
    };

    /**
     * Replaces Scene_Map.updateMain (it won't register player's movements).
     */
    Scene_Maze.prototype.updateMain = function () {
        if (!my.controller.paused) {
            var active = this.isActive();
            $gameMap.update(active);
            $gamePlayer.update(false); // Disables player's movement.
            $gameTimer.update(active);
            $gameScreen.update();
        }
    };

    /**
     * Handles user's input and updates the scene.
     */
    Scene_Maze.prototype.update = function () {
        if (!$gameMap.isEventRunning() && !$gameMessage.isBusy()) {
            if (!my.controller.paused) {
                if (Input.isTriggered('escape') || TouchInput.isCancelled()) {
                    my.controller.paused = true;
                    this.bgm = AudioManager.saveBgm();
                    AudioManager.stopBgm();
                    this.pauseWindow.open();
                    this.pauseWindow.activate();
                }

                if (Input.isPressed("shift")) {
                    this.strafing = !Input.isLastInputGamepad(); // For gamepad input, invert the behaviour, since axis 0 defaults to strafe.
                }
                else {
                    this.strafing = Input.isLastInputGamepad();
                }

                if (Input.readAxis(2, 0.5) < 0) {
                    my.player.rotateLeft();
                }
                else if (Input.readAxis(2, 0.5) > 0) {
                    my.player.rotateRight();
                }

                if (Input.isPressed("up")) {
                    my.player.moveForward();
                }
                else if (Input.isPressed("down")) {
                    my.player.moveBackward();
                }

                if (Input.isPressed("left")) {
                    if (this.strafing) {
                        my.player.strafeLeft();
                    }
                    else {
                        my.player.rotateLeft();
                    }
                }
                else if (Input.isPressed("right")) {
                    if (this.strafing) {
                        my.player.strafeRight();
                    }
                    else {
                        my.player.rotateRight();
                    }
                }

                if (TouchInput.isPressed()) {
                    var dx = TouchInput.x - Graphics.width / 2;
                    var dy = TouchInput.y - Graphics.height / 2;
                    var angle = Math.atan2(dy, dx);
                    if (angle < 0) {
                        angle += 2 * Math.PI;
                    }
                    var radius = Math.sqrt(dx * dx + dy * dy);

                    if (radius < Math.min(Graphics.width, Graphics.height) / 4) {
                        $gamePlayer.checkEventTriggerHere([0]);
                        if (!$gameMap.setupStartingEvent()) {
                            $gamePlayer.checkEventTriggerThere([0, 1, 2]);
                            $gameMap.setupStartingEvent();
                        }
                    }
                    else {
                        if (angle > Math.PI / 4 && angle < 3 * Math.PI / 4) {
                            my.player.moveBackward();
                        }
                        else if (angle > 5 * Math.PI / 4 && angle < 7 * Math.PI / 4) {
                            my.player.moveForward();
                        }
                        else if (angle > 3 * Math.PI / 4 && angle < 5 * Math.PI / 4) {
                            if (this.strafing) {
                                my.player.strafeLeft();
                            }
                            else {
                                my.player.rotateLeft();
                            }
                        }
                        else if (angle > 7 * Math.PI / 4 || angle < Math.PI / 4) {
                            if (this.strafing) {
                                my.player.strafeRight();
                            }
                            else {
                                my.player.rotateRight();
                            }
                        }
                    }
                }
            }

            my.controller.update();

            this.screenSprite._bitmap.clear();
            this.raycaster.draw();
            my.controller.drawCompass(this.screenSprite._bitmap, 40, 40, 25, "green", "red", "black");
        }

        this.updateDestination();
        this.updateMainMultiply();
        if (this.isSceneChangeOk()) {
            this.updateScene();
        } else if (SceneManager.isNextScene(Scene_Battle)) {
            this.updateEncounterEffect();
        }
        this.updateWaitCount();
        Scene_Base.prototype.update.call(this);
    };

    /**
     * Disables game's menu.
     * @returns {boolean} Always false.
     */
    Scene_Maze.prototype.isMenuEnabled = function () {
        return false;
    };

    /**
     * Creates the windows for the pause menu.
     */
    Scene_Maze.prototype.createWindows = function () {
        this.createWindowLayer();

        if (this.pauseWindow == null) {
            this.pauseWindow = new my.Maze_Window_Pause();
            this.pauseWindow.setHandler("cancel", this.resume.bind(this));
            this.pauseWindow.setHandler("retry", this.retry.bind(this));
            this.pauseWindow.setHandler("quality", this.setQuality.bind(this));
            this.pauseWindow.setHandler("quit", this.quit.bind(this));
        }

        if (this.confirmWindow == null) {
            this.confirmWindow = new my.Maze_Window_Confirm();
            this.confirmWindow.x = this.pauseWindow.x + this.pauseWindow.windowWidth();
        }

        this.addWindow(this.pauseWindow);
        this.addWindow(this.confirmWindow);
        this.addChild(this.pauseWindow);
        this.addChild(this.confirmWindow);
    };

    /**
     * Resume callback.
     */
    Scene_Maze.prototype.resume = function () {
        this.pauseWindow.close();
        this.pauseWindow.deactivate();

        my.controller.paused = false;

        this.bgm = this.bgm || $dataMap.bgm;
        AudioManager.replayBgm(this.bgm);
    };

    /**
     * Quality change callback.
     */
    Scene_Maze.prototype.setQuality = function () {
        $gameSystem.quality = ($gameSystem.quality + 1) % 4;
        this.pauseWindow.activate();
        this.pauseWindow.refresh();
        switch ($gameSystem.quality) {
            case 0: // low
                this.scaleFactor = 4;
                this.raycaster.setQuality(4, false);
                break;
            case 1: // medium
                this.scaleFactor = 2;
                this.raycaster.setQuality(2, false);
                break;
            case 2: // high
                this.scaleFactor = 1;
                this.raycaster.setQuality(1, false);
                break;
            case 3: // auto
                this.scaleFactor = 2;
                this.raycaster.setQuality(2, true);
                break;
        }
    };

    /**
     * Retry callback. Opens the confirmation window.
     */
    Scene_Maze.prototype.retry = function () {
        this.pauseWindow.deactivate();
        this.confirmWindow.y = this.pauseWindow.itemHeight() + this.pauseWindow.y;
        this.confirmWindow.setHandler("accept", this.acceptSelection.bind(this, "retry"));
        this.confirmWindow.setHandler("cancel", this.undo.bind(this));
        this.confirmWindow.open();
        this.confirmWindow.selectSymbol("cancel");
        this.confirmWindow.activate();
    };

    /**
     * Quit callback. Opens the confirmation window.
     */
    Scene_Maze.prototype.quit = function () {
        this.pauseWindow.deactivate();
        this.confirmWindow.y = this.pauseWindow.itemHeight() * 2 + this.pauseWindow.y;
        this.confirmWindow.setHandler("accept", this.acceptSelection.bind(this, "quit"));
        this.confirmWindow.setHandler("cancel", this.undo.bind(this));
        this.confirmWindow.open();
        this.confirmWindow.selectSymbol("cancel");
        this.confirmWindow.activate();
    };

    /**
     * Confirmation window's undo callback.
     */
    Scene_Maze.prototype.undo = function () {
        this.confirmWindow.close();
        this.confirmWindow.deactivate();
        this.pauseWindow.activate();
    };

    /**
     * Confirmation window's accept callback. Performs the requested command.
     * @param cmd Requested command.
     */
    Scene_Maze.prototype.acceptSelection = function (cmd) {
        this.confirmWindow.close();
        this.confirmWindow.deactivate();
        this.pauseWindow.close();

        switch (cmd) {
            case "retry":
                my.controller.reset();
                this.raycaster.draw();
                break;
            case "quit":
                if (my.isMaze) {
                    $mazeClear = false;
                    $gamePlayer.reserveTransfer(my.oldPosition.id, my.oldPosition.x, my.oldPosition.y, my.oldPosition.direction, 2);
                }

                SceneManager.goto(Scene_Map);
                break;
        }
    };

    return my;
}(Maze || {}));