<commit_before>var Packet = require('./packet');
var GameServer = require('./GameServer');

function PlayerTracker(gameServer, socket) {
    this.pID = -1;
    this.disconnect = -1; // Disconnection
    this.name = "";
    this.skin = "";
    this.gameServer = gameServer;
    this.socket = socket;
    this.nodeAdditionQueue = [];
    this.nodeDestroyQueue = [];
    this.visibleNodes = [];
    this.collidingNodes = []; // Perfomance save; all nodes colliding with player's cells
    this.cells = [];
    this.mergeOverride = false; // Triggered by console command
    this.score = 0; // Needed for leaderboard
    this.scale = 1;
    this.isMassChanged = true;

    this.mouse = {
        x: 0,
        y: 0
    };
    this.shouldMoveCells = true; // False if the mouse packet wasn't triggered
    this.notMoved = false; // If one of cells have been moved after splitting this is triggered
    this.movePacketTriggered = false;
    this.mouseCells = []; // For individual cell movement
    this.tickLeaderboard = 0;
    this.tickViewBox = 0;

    this.team = 0;
    this.spectate = false;
    this.freeRoam = false; // Free-roam mode enables player to move in spectate mode

    // Viewing box
    this.sightRangeX = 0;
    this.sightRangeY = 0;
    this.centerPos = { // Center of map
        x: 3000,
        y: 3000
    };
    this.viewBox = {
        topY: 0,
        bottomY: 0,
        leftX: 0,
        rightX: 0,
        width: 0, // Half-width
        height: 0 // Half-height
    };

    // Scramble the coordinate system for anti-raga
    this.scrambleX = 0;
    this.scrambleY = 0;

    // Gamemode function
    if (gameServer) {
        // Find center
        var width = gameServer.config.borderRight - gameServer.config.borderLeft;
        var height = gameServer.config.borderBottom - gameServer.config.borderTop;
        this.centerPos.x = gameServer.config.borderLeft + width / 2;
        this.centerPos.y = gameServer.config.borderTop + height / 2;
        // Player id
        this.pID = gameServer.getNewPlayerID();
        // Gamemode function
        gameServer.gameMode.onPlayerInit(this);
        // Only scramble if enabled in config
        if (gameServer.config.serverScrambleCoords == 1) {
            this.scrambleX = Math.floor((1 << 15) * Math.random());
            this.scrambleY = Math.floor((1 << 15) * Math.random());
        }
    }
}

module.exports = PlayerTracker;

// Setters/Getters

PlayerTracker.prototype.setName = function(name) {
    this.name = name;
};

PlayerTracker.prototype.getName = function() {
    return this.name;
};

PlayerTracker.prototype.setSkin = function (skin) {
    this.skin = skin;
};

PlayerTracker.prototype.getSkin = function () {
    return this.skin;
};

PlayerTracker.prototype.getScore = function(reCalcScore) {
    if (this.isMassChanged)
        this.updateMass();
    return this.score >> 0;
};

PlayerTracker.prototype.getScale = function () {
    if (this.isMassChanged)
        this.updateMass();
    return this.scale;
};

PlayerTracker.prototype.updateMass = function () {
    var totalSize = 0;
    for (var i = 0; i < this.cells.length; i++) {
        var node = this.cells[i];
        if (!node) continue;
        totalSize += node.getSize();
    }
    if (totalSize == 0) {
        //this.scale = 1;
        this.score = 0;
    } else {
        this.score = totalSize * totalSize / 100;
        this.scale = Math.pow(Math.min(64 / totalSize, 1), 0.4);
    }
    this.isMassChanged = false;
};

PlayerTracker.prototype.massChanged = function () {
    this.isMassChanged = true;
};

PlayerTracker.prototype.setColor = function(color) {
    this.color.r = color.r;
    this.color.g = color.g;
    this.color.b = color.b;
};

PlayerTracker.prototype.getTeam = function() {
    return this.team;
};

// Functions

PlayerTracker.prototype.update = function () {
    // if initialization is not complete yet then do not update
    if (this.socket.packetHandler.protocol == 0)
        return;

    // First reset colliding nodes
    this.collidingNodes = [];

    // Move packet update
    if (this.movePacketTriggered) {
        this.movePacketTriggered = false;
        this.shouldMoveCells = true;
    } else {
        this.shouldMoveCells = false;
    }
    // Actions buffer (So that people cant spam packets)
    if (this.socket.packetHandler.pressSpace) { // Split cell
        if (!this.mergeOverride) this.gameServer.gameMode.pressSpace(this.gameServer, this);
        this.socket.packetHandler.pressSpace = false;
    }

    if (this.socket.packetHandler.pressW) { // Eject mass
        this.gameServer.gameMode.pressW(this.gameServer, this);
        this.socket.packetHandler.pressW = false;
    }

    if (this.socket.packetHandler.pressQ) { // Q Press
        this.gameServer.gameMode.pressQ(this.gameServer, this);
        this.socket.packetHandler.pressQ = false;
    }

    var updateNodes = []; // Nodes that need to be updated via packet

    // Remove nodes from visible nodes if possible
    var d = 0;
    while (d < this.nodeDestroyQueue.length) {
        var index = this.visibleNodes.indexOf(this.nodeDestroyQueue[d]);
        if (index > -1) {
            this.visibleNodes.splice(index, 1);
            d++; // Increment
        } else {
            // Node was never visible anyways
            this.nodeDestroyQueue.splice(d, 1);
        }
    }

    // Get visible nodes every 400 ms
    var nonVisibleNodes = []; // Nodes that are not visible
    if (this.tickViewBox <= 0) {
        var newVisible = this.calcViewBox();
        try { // Add a try block in any case

            // Compare and destroy nodes that are not seen
            for (var i = 0; i < this.visibleNodes.length; i++) {
                var index = newVisible.indexOf(this.visibleNodes[i]);
                if (index == -1) {
                    // Not seen by the client anymore
                    nonVisibleNodes.push(this.visibleNodes[i]);
                }
            }

            // Add nodes to client's screen if client has not seen it already
            for (var i = 0; i < newVisible.length; i++) {
                var index = this.visibleNodes.indexOf(newVisible[i]);
                if (index == -1) {
                    updateNodes.push(newVisible[i]);
                }
            }
        } catch (err) {
            console.error(err);
        }

        this.visibleNodes = newVisible;
        // Reset Ticks
        this.tickViewBox = 0;
    } else {
        this.tickViewBox--;
        // Add nodes to screen
        for (var i = 0; i < this.nodeAdditionQueue.length; i++) {
            var node = this.nodeAdditionQueue[i];
            this.visibleNodes.push(node);
            updateNodes.push(node);
        }
    }

    // Update moving nodes
    for (var i = 0; i < this.visibleNodes.length; i++) {
        var node = this.visibleNodes[i];
        if (node.sendUpdate()) {
            // Sends an update if cell is moving
            updateNodes.push(node);
        }
    }

    // Send packet
    this.socket.sendPacket(new Packet.UpdateNodes(
        this.nodeDestroyQueue,
            updateNodes,
            nonVisibleNodes,
            this.scrambleX,
            this.scrambleY
    ));

    this.nodeDestroyQueue = []; // Reset destroy queue
    this.nodeAdditionQueue = []; // Reset addition queue

    // Update leaderboard
    if (this.tickLeaderboard <= 0) {
        if (this.gameServer.lb_packet != null)
            this.socket.sendPacket(this.gameServer.lb_packet);
        this.tickLeaderboard = 50;
    } else {
        this.tickLeaderboard--;
    }

    // Handles disconnections
    if (this.disconnect > -1) {
        // Player has disconnected... remove it when the timer hits -1
        this.disconnect--;
        // Also remove it when its cells are completely eaten not to back up dead clients
        if (this.disconnect == -1 || this.cells.length == 0) {
            // Remove all client cells
            var len = this.cells.length;
            for (var i = 0; i < len; i++) {
                var cell = this.socket.playerTracker.cells[0];

                if (!cell) {
                    continue;
                }

                this.gameServer.removeNode(cell);
            }

            // Remove from client list
            var index = this.gameServer.clients.indexOf(this.socket);
            if (index != -1) {
                this.gameServer.clients.splice(index, 1);
            }
        }
    }
};

// Viewing box

PlayerTracker.prototype.updateSightRange = function() { // For view distance
    var scale = this.getScale();
    this.sightRangeX = (this.gameServer.config.serverViewBaseX + 100) / scale;
    this.sightRangeY = (this.gameServer.config.serverViewBaseY + 100) / scale;
};

PlayerTracker.prototype.updateCenter = function() { // Get center of cells
    var len = this.cells.length;
    if (len <= 0) return;
    var cx = 0;
    var cy = 0;
    var count = 0;
    for (var i = 0; i < len; i++) {
        var node = this.cells[i];
        if (!node) continue;
        cx += node.position.x;
        cy += node.position.y;
        count++;
    }
    if (count == 0) return;
    this.centerPos.x = cx / count;
    this.centerPos.y = cy / count;
};

PlayerTracker.prototype.calcViewBox = function() {
    if (this.spectate) {
        // Spectate mode
        return this.getSpectateNodes();
    }

    // Main function
    this.updateSightRange();
    this.updateCenter();

    // Box
    this.viewBox.topY = this.centerPos.y - this.sightRangeY / 2;
    this.viewBox.bottomY = this.centerPos.y + this.sightRangeY / 2;
    this.viewBox.leftX = this.centerPos.x - this.sightRangeX / 2;
    this.viewBox.rightX = this.centerPos.x + this.sightRangeX / 2;
    this.viewBox.width = this.sightRangeX;
    this.viewBox.height = this.sightRangeY;

    var newVisible = this.calcVisibleNodes();

    return newVisible;
};

PlayerTracker.prototype.getSpectateNodes = function() {
    var specPlayer = this.gameServer.largestClient;

    if (!this.freeRoam) {

        if (!specPlayer) return this.moveInFreeRoam(); // There are probably no players

        // Get spectate player's location and calculate zoom amount
        var specZoom = Math.min(Math.sqrt(100 * specPlayer.getScore(false)), 555);
        specZoom = Math.pow(Math.min(40.5 / specZoom, 1.0), 0.4) * 0.8;

        this.setCenterPos(specPlayer.centerPos.x, specPlayer.centerPos.y);
        this.sendPosPacket(specZoom);

        return specPlayer.visibleNodes.slice(0);
    }
    // Behave like client is in free-roam as function didn't return nodes
    return this.moveInFreeRoam();
};

PlayerTracker.prototype.moveInFreeRoam = function() {
    // User is in free roam
    // To mimic agar.io, get distance from center to mouse and apply a part of the distance

    var dist = this.gameServer.getDist(this.mouse.x, this.mouse.y, this.centerPos.x, this.centerPos.y);
    var angle = this.getAngle(this.mouse.x, this.mouse.y, this.centerPos.x, this.centerPos.y);
    var speed = Math.min(dist / 10, 70); // Not to break laws of universe by going faster than light speed

    this.centerPos.x += speed * Math.sin(angle);
    this.centerPos.y += speed * Math.cos(angle);

    // Check if went away from borders
    this.checkBorderPass();

    // Now that we've updated center pos, get nearby cells
    // We're going to use config's view base times 2.5

    var mult = 3.5; // To simplify multiplier, in case this needs editing later on
    var baseX = this.gameServer.config.serverViewBaseX;
    var baseY = this.gameServer.config.serverViewBaseY;

    this.viewBox.topY = this.centerPos.y - baseY * mult;
    this.viewBox.bottomY = this.centerPos.y + baseY * mult;
    this.viewBox.leftX = this.centerPos.x - baseX * mult;
    this.viewBox.rightX = this.centerPos.x + baseX * mult;
    this.viewBox.width = baseX * mult;
    this.viewBox.height = baseY * mult;

    // Use calcViewBox's way of looking for nodes
    var newVisible = this.calcVisibleNodes();
    var specZoom = 222;
    specZoom = Math.pow(Math.min(40.5 / specZoom, 1.0), 0.4) * 0.6; // Constant zoom
    this.sendPosPacket(specZoom);
    return newVisible;
};

PlayerTracker.prototype.calcVisibleNodes = function() {
    var newVisible = [];
    for (var i = 0; i < this.gameServer.nodes.length; i++) {
        var node = this.gameServer.nodes[i];
        if (!node) continue;

        var check = node.visibleCheck(this.viewBox, this.centerPos, this.cells);
        if (check > 0 || node.owner == this) {
            // Cell is in range of viewBox
            newVisible.push(node);
            // Check if it's colliding with one of player's cells
            if (check == 2) this.collidingNodes.push(node);
        }
    }
    return newVisible;
};

PlayerTracker.prototype.setCenterPos = function(x, y) {
    this.centerPos.x = x;
    this.centerPos.y = y;
    if (this.freeRoam) this.checkBorderPass();
};

PlayerTracker.prototype.checkBorderPass = function() {
    // A check while in free-roam mode to avoid player going into nothingness
    if (this.centerPos.x < this.gameServer.config.borderLeft) {
        this.centerPos.x = this.gameServer.config.borderLeft;
    }
    if (this.centerPos.x > this.gameServer.config.borderRight) {
        this.centerPos.x = this.gameServer.config.borderRight;
    }
    if (this.centerPos.y < this.gameServer.config.borderTop) {
        this.centerPos.y = this.gameServer.config.borderTop;
    }
    if (this.centerPos.y > this.gameServer.config.borderBottom) {
        this.centerPos.y = this.gameServer.config.borderBottom;
    }
};

PlayerTracker.prototype.sendPosPacket = function(specZoom) {
    // TODO: Send packet elsewhere so it is sent more often
    this.socket.sendPacket(new Packet.UpdatePosition(
        this.centerPos.x + this.scrambleX,
        this.centerPos.y + this.scrambleY,
        specZoom
    ));
};

PlayerTracker.prototype.sendCustomPosPacket = function(x, y, specZoom) {
    // TODO: Send packet elsewhere so it is sent more often
    this.socket.sendPacket(new Packet.UpdatePosition(
        x + this.scrambleX,
        y + this.scrambleY,
        specZoom
    ));
};

PlayerTracker.prototype.getAngle = function(x1, y1, x2, y2) {
    var deltaY = y1 - y2;
    var deltaX = x1 - x2;
    return Math.atan2(deltaX, deltaY);
};
<commit_msg>fix sight area and scale for spectate<commit_after>var Packet = require('./packet');
var GameServer = require('./GameServer');

function PlayerTracker(gameServer, socket) {
    this.pID = -1;
    this.disconnect = -1; // Disconnection
    this.name = "";
    this.skin = "";
    this.gameServer = gameServer;
    this.socket = socket;
    this.nodeAdditionQueue = [];
    this.nodeDestroyQueue = [];
    this.visibleNodes = [];
    this.collidingNodes = []; // Perfomance save; all nodes colliding with player's cells
    this.cells = [];
    this.mergeOverride = false; // Triggered by console command
    this.score = 0; // Needed for leaderboard
    this.scale = 1;
    this.isMassChanged = true;

    this.mouse = {
        x: 0,
        y: 0
    };
    this.shouldMoveCells = true; // False if the mouse packet wasn't triggered
    this.notMoved = false; // If one of cells have been moved after splitting this is triggered
    this.movePacketTriggered = false;
    this.mouseCells = []; // For individual cell movement
    this.tickLeaderboard = 0;
    this.tickViewBox = 0;

    this.team = 0;
    this.spectate = false;
    this.freeRoam = false; // Free-roam mode enables player to move in spectate mode

    // Viewing box
    this.sightRangeX = 0;
    this.sightRangeY = 0;
    this.centerPos = { // Center of map
        x: 3000,
        y: 3000
    };
    this.viewBox = {
        topY: 0,
        bottomY: 0,
        leftX: 0,
        rightX: 0,
        width: 0, // Half-width
        height: 0 // Half-height
    };

    // Scramble the coordinate system for anti-raga
    this.scrambleX = 0;
    this.scrambleY = 0;

    // Gamemode function
    if (gameServer) {
        // Find center
        var width = gameServer.config.borderRight - gameServer.config.borderLeft;
        var height = gameServer.config.borderBottom - gameServer.config.borderTop;
        this.centerPos.x = gameServer.config.borderLeft + width / 2;
        this.centerPos.y = gameServer.config.borderTop + height / 2;
        // Player id
        this.pID = gameServer.getNewPlayerID();
        // Gamemode function
        gameServer.gameMode.onPlayerInit(this);
        // Only scramble if enabled in config
        if (gameServer.config.serverScrambleCoords == 1) {
            this.scrambleX = Math.floor((1 << 15) * Math.random());
            this.scrambleY = Math.floor((1 << 15) * Math.random());
        }
    }
}

module.exports = PlayerTracker;

// Setters/Getters

PlayerTracker.prototype.setName = function(name) {
    this.name = name;
};

PlayerTracker.prototype.getName = function() {
    return this.name;
};

PlayerTracker.prototype.setSkin = function (skin) {
    this.skin = skin;
};

PlayerTracker.prototype.getSkin = function () {
    return this.skin;
};

PlayerTracker.prototype.getScore = function(reCalcScore) {
    if (this.isMassChanged)
        this.updateMass();
    return this.score >> 0;
};

PlayerTracker.prototype.getScale = function () {
    if (this.isMassChanged)
        this.updateMass();
    return this.scale;
};

PlayerTracker.prototype.updateMass = function () {
    var totalSize = 0;
    for (var i = 0; i < this.cells.length; i++) {
        var node = this.cells[i];
        if (!node) continue;
        totalSize += node.getSize();
    }
    if (totalSize == 0) {
        //this.scale = 1;
        this.score = 0;
    } else {
        this.score = totalSize * totalSize / 100;
        this.scale = Math.pow(Math.min(64 / totalSize, 1), 0.4);
    }
    this.isMassChanged = false;
};

PlayerTracker.prototype.massChanged = function () {
    this.isMassChanged = true;
};

PlayerTracker.prototype.setColor = function(color) {
    this.color.r = color.r;
    this.color.g = color.g;
    this.color.b = color.b;
};

PlayerTracker.prototype.getTeam = function() {
    return this.team;
};

// Functions

PlayerTracker.prototype.update = function () {
    // if initialization is not complete yet then do not update
    if (this.socket.packetHandler.protocol == 0)
        return;

    // First reset colliding nodes
    this.collidingNodes = [];

    // Move packet update
    if (this.movePacketTriggered) {
        this.movePacketTriggered = false;
        this.shouldMoveCells = true;
    } else {
        this.shouldMoveCells = false;
    }
    // Actions buffer (So that people cant spam packets)
    if (this.socket.packetHandler.pressSpace) { // Split cell
        if (!this.mergeOverride) this.gameServer.gameMode.pressSpace(this.gameServer, this);
        this.socket.packetHandler.pressSpace = false;
    }

    if (this.socket.packetHandler.pressW) { // Eject mass
        this.gameServer.gameMode.pressW(this.gameServer, this);
        this.socket.packetHandler.pressW = false;
    }

    if (this.socket.packetHandler.pressQ) { // Q Press
        this.gameServer.gameMode.pressQ(this.gameServer, this);
        this.socket.packetHandler.pressQ = false;
    }

    var updateNodes = []; // Nodes that need to be updated via packet

    // Remove nodes from visible nodes if possible
    var d = 0;
    while (d < this.nodeDestroyQueue.length) {
        var index = this.visibleNodes.indexOf(this.nodeDestroyQueue[d]);
        if (index > -1) {
            this.visibleNodes.splice(index, 1);
            d++; // Increment
        } else {
            // Node was never visible anyways
            this.nodeDestroyQueue.splice(d, 1);
        }
    }

    // Get visible nodes every 400 ms
    var nonVisibleNodes = []; // Nodes that are not visible
    if (this.tickViewBox <= 0) {
        var newVisible = this.calcViewBox();
        try { // Add a try block in any case

            // Compare and destroy nodes that are not seen
            for (var i = 0; i < this.visibleNodes.length; i++) {
                var index = newVisible.indexOf(this.visibleNodes[i]);
                if (index == -1) {
                    // Not seen by the client anymore
                    nonVisibleNodes.push(this.visibleNodes[i]);
                }
            }

            // Add nodes to client's screen if client has not seen it already
            for (var i = 0; i < newVisible.length; i++) {
                var index = this.visibleNodes.indexOf(newVisible[i]);
                if (index == -1) {
                    updateNodes.push(newVisible[i]);
                }
            }
        } catch (err) {
            console.error(err);
        }

        this.visibleNodes = newVisible;
        // Reset Ticks
        this.tickViewBox = 0;
    } else {
        this.tickViewBox--;
        // Add nodes to screen
        for (var i = 0; i < this.nodeAdditionQueue.length; i++) {
            var node = this.nodeAdditionQueue[i];
            this.visibleNodes.push(node);
            updateNodes.push(node);
        }
    }

    // Update moving nodes
    for (var i = 0; i < this.visibleNodes.length; i++) {
        var node = this.visibleNodes[i];
        if (node.sendUpdate()) {
            // Sends an update if cell is moving
            updateNodes.push(node);
        }
    }

    // Send packet
    this.socket.sendPacket(new Packet.UpdateNodes(
        this.nodeDestroyQueue,
            updateNodes,
            nonVisibleNodes,
            this.scrambleX,
            this.scrambleY
    ));

    this.nodeDestroyQueue = []; // Reset destroy queue
    this.nodeAdditionQueue = []; // Reset addition queue

    // Update leaderboard
    if (this.tickLeaderboard <= 0) {
        if (this.gameServer.lb_packet != null)
            this.socket.sendPacket(this.gameServer.lb_packet);
        this.tickLeaderboard = 50;
    } else {
        this.tickLeaderboard--;
    }

    // Handles disconnections
    if (this.disconnect > -1) {
        // Player has disconnected... remove it when the timer hits -1
        this.disconnect--;
        // Also remove it when its cells are completely eaten not to back up dead clients
        if (this.disconnect == -1 || this.cells.length == 0) {
            // Remove all client cells
            var len = this.cells.length;
            for (var i = 0; i < len; i++) {
                var cell = this.socket.playerTracker.cells[0];

                if (!cell) {
                    continue;
                }

                this.gameServer.removeNode(cell);
            }

            // Remove from client list
            var index = this.gameServer.clients.indexOf(this.socket);
            if (index != -1) {
                this.gameServer.clients.splice(index, 1);
            }
        }
    }
};

// Viewing box

PlayerTracker.prototype.updateSightRange = function() { // For view distance
    var scale = this.getScale();
    this.sightRangeX = (this.gameServer.config.serverViewBaseX + 100) / scale;
    this.sightRangeY = (this.gameServer.config.serverViewBaseY + 100) / scale;
};

PlayerTracker.prototype.updateCenter = function() { // Get center of cells
    var len = this.cells.length;
    if (len <= 0) return;
    var cx = 0;
    var cy = 0;
    var count = 0;
    for (var i = 0; i < len; i++) {
        var node = this.cells[i];
        if (!node) continue;
        cx += node.position.x;
        cy += node.position.y;
        count++;
    }
    if (count == 0) return;
    this.centerPos.x = cx / count;
    this.centerPos.y = cy / count;
};

PlayerTracker.prototype.calcViewBox = function() {
    if (this.spectate) {
        // Spectate mode
        return this.getSpectateNodes();
    }

    // Main function
    this.updateSightRange();
    this.updateCenter();

    // Box
    this.viewBox.topY = this.centerPos.y - this.sightRangeY / 2;
    this.viewBox.bottomY = this.centerPos.y + this.sightRangeY / 2;
    this.viewBox.leftX = this.centerPos.x - this.sightRangeX / 2;
    this.viewBox.rightX = this.centerPos.x + this.sightRangeX / 2;
    this.viewBox.width = this.sightRangeX;
    this.viewBox.height = this.sightRangeY;

    var newVisible = this.calcVisibleNodes();

    return newVisible;
};

PlayerTracker.prototype.getSpectateNodes = function() {
    var specPlayer = this.gameServer.largestClient;

    if (!this.freeRoam) {

        if (!specPlayer) return this.moveInFreeRoam(); // There are probably no players

        // Get spectate player's location and calculate zoom amount
        this.scale = specPlayer.getScale();
        this.setCenterPos(specPlayer.centerPos.x, specPlayer.centerPos.y);
        this.sendPosPacket(this.scale);

        return specPlayer.visibleNodes.slice(0);
    }
    // Behave like client is in free-roam as function didn't return nodes
    return this.moveInFreeRoam();
};

PlayerTracker.prototype.moveInFreeRoam = function() {
    // User is in free roam
    // To mimic agar.io, get distance from center to mouse and apply a part of the distance

    var dist = this.gameServer.getDist(this.mouse.x, this.mouse.y, this.centerPos.x, this.centerPos.y);
    var angle = this.getAngle(this.mouse.x, this.mouse.y, this.centerPos.x, this.centerPos.y);
    var speed = Math.min(dist / 10, 70); // Not to break laws of universe by going faster than light speed

    this.centerPos.x += speed * Math.sin(angle);
    this.centerPos.y += speed * Math.cos(angle);

    // Check if went away from borders
    this.checkBorderPass();

    // Now that we've updated center pos, get nearby cells

    this.scale = 0.25;
    var baseX = (this.gameServer.config.serverViewBaseX + 100) / this.scale;
    var baseY = (this.gameServer.config.serverViewBaseY + 100) / this.scale;

    this.viewBox.leftX = this.centerPos.x - baseX / 2;
    this.viewBox.topY = this.centerPos.y - baseY / 2;
    this.viewBox.rightX = this.centerPos.x + baseX / 2;
    this.viewBox.bottomY = this.centerPos.y + baseY / 2;
    this.viewBox.width = baseX;
    this.viewBox.height = baseY;

    // Use calcViewBox's way of looking for nodes
    var newVisible = this.calcVisibleNodes();
    this.sendPosPacket(this.scale);
    return newVisible;
};

PlayerTracker.prototype.calcVisibleNodes = function() {
    var newVisible = [];
    for (var i = 0; i < this.gameServer.nodes.length; i++) {
        var node = this.gameServer.nodes[i];
        if (!node) continue;

        var check = node.visibleCheck(this.viewBox, this.centerPos, this.cells);
        if (check > 0 || node.owner == this) {
            // Cell is in range of viewBox
            newVisible.push(node);
            // Check if it's colliding with one of player's cells
            if (check == 2) this.collidingNodes.push(node);
        }
    }
    return newVisible;
};

PlayerTracker.prototype.setCenterPos = function(x, y) {
    this.centerPos.x = x;
    this.centerPos.y = y;
    if (this.freeRoam) this.checkBorderPass();
};

PlayerTracker.prototype.checkBorderPass = function() {
    // A check while in free-roam mode to avoid player going into nothingness
    if (this.centerPos.x < this.gameServer.config.borderLeft) {
        this.centerPos.x = this.gameServer.config.borderLeft;
    }
    if (this.centerPos.x > this.gameServer.config.borderRight) {
        this.centerPos.x = this.gameServer.config.borderRight;
    }
    if (this.centerPos.y < this.gameServer.config.borderTop) {
        this.centerPos.y = this.gameServer.config.borderTop;
    }
    if (this.centerPos.y > this.gameServer.config.borderBottom) {
        this.centerPos.y = this.gameServer.config.borderBottom;
    }
};

PlayerTracker.prototype.sendPosPacket = function(specZoom) {
    // TODO: Send packet elsewhere so it is sent more often
    this.socket.sendPacket(new Packet.UpdatePosition(
        this.centerPos.x + this.scrambleX,
        this.centerPos.y + this.scrambleY,
        specZoom
    ));
};

PlayerTracker.prototype.sendCustomPosPacket = function(x, y, specZoom) {
    // TODO: Send packet elsewhere so it is sent more often
    this.socket.sendPacket(new Packet.UpdatePosition(
        x + this.scrambleX,
        y + this.scrambleY,
        specZoom
    ));
};

PlayerTracker.prototype.getAngle = function(x1, y1, x2, y2) {
    var deltaY = y1 - y2;
    var deltaX = x1 - x2;
    return Math.atan2(deltaX, deltaY);
};
<|endoftext|>


<commit_before>/**
    PaymentAcount activity
**/
'use strict';

var Activity = require('../components/Activity');
var ko = require('knockout');

var A = Activity.extend(function PaymentAccountActivity() {

    Activity.apply(this, arguments);

    this.viewModel = new ViewModel(this.app);
    this.accessLevel = this.app.UserType.serviceProfessional;
    this.navBar = Activity.createSubsectionNavBar('Marketplace profile', {
        backLink: '/marketplaceProfile' , helpLink: '/help/relatedArticles/201967096-accepting-and-receiving-payments'
    });

    this.registerHandler({
        target: this.app.model.paymentAccount,
        event: 'error',
        handler: function(err) {
            var msg = err.task === 'save' ? 'Error saving your payment account.' : 'Error loading your payment account.';
            this.app.modals.showError({
                title: msg,
                error: err && err.task && err.error || err
            });
        }.bind(this)
    });
});

exports.init = A.init;

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    // Discard any previous unsaved edit
    this.viewModel.discard();
    // Keep data updated:
    this.app.model.paymentAccount.sync();
};

function ViewModel(app) {

    var paymentAccount = app.model.paymentAccount;
    this.errorMessages = paymentAccount.errorMessages;

    var dataVersion = paymentAccount.newVersion();
    dataVersion.isObsolete.subscribe(function(itIs) {
        if (itIs) {
            // new version from server while editing
            // FUTURE: warn about a new remote version asking
            // confirmation to load them or discard and overwrite them;
            // the same is need on save(), and on server response
            // with a 509:Conflict status (its body must contain the
            // server version).
            // Right now, just overwrite current changes with
            // remote ones:
            dataVersion.pull({ evenIfNewer: true });
            this.formVisible(!dataVersion.version.status());
        }
    }.bind(this));

    // Actual data for the form:
    this.paymentAccount = dataVersion.version;

    this.isLocked = paymentAccount.isLocked;

    this.submitText = ko.pureComputed(function() {
        return (
            this.isLoading() ?
                'loading...' :
                this.isSaving() ?
                    'saving...' :
                    'Save'
        );
    }, paymentAccount);

    this.discard = function discard() {
        dataVersion.pull({ evenIfNewer: true });
        this.formVisible(!dataVersion.version.status());
        this.userSelectedBank(null);
    }.bind(this);

    this.save = function save() {
        dataVersion.pushSave()
        .then(function() {
            app.successSave();
        })
        .catch(function() {
            // catch error, managed on event
        });
    }.bind(this);

    this.errorMessages = {
        postalCode: ko.observable()
    };

    // On change to a valid code, do remote look-up
    var postalError = this.errorMessages.postalCode;
    ko.computed(function() {
        if (!paymentAccount.isLoading()) {
            var postalCode = this.postalCode();
            if (postalCode && !/^\s*$/.test(postalCode)) {
                app.model.postalCodes.getItem(postalCode)
                .then(function(info) {
                    if (info) {
                        this.city(info.city);
                        this.stateProvinceCode(info.stateProvinceCode);
                        postalError('');
                    }
                }.bind(this))
                .catch(function(err) {
                    this.city('');
                    this.stateProvinceCode('');
                    // Expected errors, a single message, set
                    // on the observable
                    var msg = typeof(err) === 'string' ? err : null;
                    if (msg || err && err.responseJSON && err.responseJSON.errorMessage) {
                        postalError(msg || err.responseJSON.errorMessage);
                    }
                    else {
                        // Log to console for debugging purposes, on regular use an error on the
                        // postal code is not critical and can be transparent; if there are
                        // connectivity or authentification errors will throw on saving the address
                        console.error('Server error validating Postal Code', err);
                    }
                }.bind(this));
            }
        }
    }, this.paymentAccount)
    // Avoid excessive requests by setting a timeout since the latest change
    .extend({ rateLimit: { timeout: 60, method: 'notifyWhenChangesStop' } });

    this.formVisible = ko.observable(false);
    this.showForm = function() {
        this.formVisible(true);
    };

    // Null by default, since it represents an immediate selection from the user for current session.
    this.userSelectedBank = ko.observable(null);
    this.isVenmoAccount = ko.pureComputed(function() {
        // Quick return: on user selection
        if (this.userSelectedBank()) return false;
        // On new record, no status, show as 'is bank', so 'false' here:
        if (!this.paymentAccount.status()) return false;
        // If there is no bank data, is Venmo
        return !(this.paymentAccount.accountNumber() && this.paymentAccount.routingNumber());
    }, this);
    this.isBankAccount = ko.pureComputed(function() {
        return !this.isVenmoAccount();
    }, this);

    this.chooseVenmoAccount = function() {
        this.userSelectedBank(false);
    }.bind(this);
    this.chooseBankAccount = function() {
        this.userSelectedBank(true);
    }.bind(this);
}
<commit_msg>PaymentAccount: Yet another fix type of account selection #1011<commit_after>/**
    PaymentAcount activity
**/
'use strict';

var Activity = require('../components/Activity');
var ko = require('knockout');

var A = Activity.extend(function PaymentAccountActivity() {

    Activity.apply(this, arguments);

    this.viewModel = new ViewModel(this.app);
    this.accessLevel = this.app.UserType.serviceProfessional;
    this.navBar = Activity.createSubsectionNavBar('Marketplace profile', {
        backLink: '/marketplaceProfile' , helpLink: '/help/relatedArticles/201967096-accepting-and-receiving-payments'
    });

    this.registerHandler({
        target: this.app.model.paymentAccount,
        event: 'error',
        handler: function(err) {
            var msg = err.task === 'save' ? 'Error saving your payment account.' : 'Error loading your payment account.';
            this.app.modals.showError({
                title: msg,
                error: err && err.task && err.error || err
            });
        }.bind(this)
    });
});

exports.init = A.init;

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    // Discard any previous unsaved edit
    this.viewModel.discard();
    // Keep data updated:
    this.app.model.paymentAccount.sync();
};

function ViewModel(app) {

    var paymentAccount = app.model.paymentAccount;
    this.errorMessages = paymentAccount.errorMessages;

    var dataVersion = paymentAccount.newVersion();
    dataVersion.isObsolete.subscribe(function(itIs) {
        if (itIs) {
            // new version from server while editing
            // FUTURE: warn about a new remote version asking
            // confirmation to load them or discard and overwrite them;
            // the same is need on save(), and on server response
            // with a 509:Conflict status (its body must contain the
            // server version).
            // Right now, just overwrite current changes with
            // remote ones:
            dataVersion.pull({ evenIfNewer: true });
            this.formVisible(!dataVersion.version.status());
        }
    }.bind(this));

    // Actual data for the form:
    this.paymentAccount = dataVersion.version;

    this.isLocked = paymentAccount.isLocked;

    this.submitText = ko.pureComputed(function() {
        return (
            this.isLoading() ?
                'loading...' :
                this.isSaving() ?
                    'saving...' :
                    'Save'
        );
    }, paymentAccount);

    this.discard = function discard() {
        dataVersion.pull({ evenIfNewer: true });
        this.formVisible(!dataVersion.version.status());
        this.userSelectedAccount(null);
    }.bind(this);

    this.save = function save() {
        dataVersion.pushSave()
        .then(function() {
            app.successSave();
        })
        .catch(function() {
            // catch error, managed on event
        });
    }.bind(this);

    this.errorMessages = {
        postalCode: ko.observable()
    };

    // On change to a valid code, do remote look-up
    var postalError = this.errorMessages.postalCode;
    ko.computed(function() {
        if (!paymentAccount.isLoading()) {
            var postalCode = this.postalCode();
            if (postalCode && !/^\s*$/.test(postalCode)) {
                app.model.postalCodes.getItem(postalCode)
                .then(function(info) {
                    if (info) {
                        this.city(info.city);
                        this.stateProvinceCode(info.stateProvinceCode);
                        postalError('');
                    }
                }.bind(this))
                .catch(function(err) {
                    this.city('');
                    this.stateProvinceCode('');
                    // Expected errors, a single message, set
                    // on the observable
                    var msg = typeof(err) === 'string' ? err : null;
                    if (msg || err && err.responseJSON && err.responseJSON.errorMessage) {
                        postalError(msg || err.responseJSON.errorMessage);
                    }
                    else {
                        // Log to console for debugging purposes, on regular use an error on the
                        // postal code is not critical and can be transparent; if there are
                        // connectivity or authentification errors will throw on saving the address
                        console.error('Server error validating Postal Code', err);
                    }
                }.bind(this));
            }
        }
    }, this.paymentAccount)
    // Avoid excessive requests by setting a timeout since the latest change
    .extend({ rateLimit: { timeout: 60, method: 'notifyWhenChangesStop' } });

    this.formVisible = ko.observable(false);
    this.showForm = function() {
        this.formVisible(true);
    };

    // Null by default, since it represents an immediate selection from the user for current session.
    this.userSelectedAccount = ko.observable(null);
    this.isVenmoAccount = ko.pureComputed(function() {
        // Quick return: on user selection
        if (this.userSelectedAccount()) {
            return this.userSelectedAccount() === 'venmo';
        }
        // On new record, no status, show as 'is bank', so 'false' here:
        if (!this.paymentAccount.status()) return false;
        // If there is no bank data, is Venmo
        return !(this.paymentAccount.accountNumber() && this.paymentAccount.routingNumber());
    }, this);
    this.isBankAccount = ko.pureComputed(function() {
        return !this.isVenmoAccount();
    }, this);

    this.chooseVenmoAccount = function() {
        this.userSelectedAccount('venmo');
    }.bind(this);
    this.chooseBankAccount = function() {
        this.userSelectedAccount('bank');
    }.bind(this);
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** Make a Binary Action. **/
function makeBinaryOp(name, sym, keys, f) {
  f.toString = function() { return sym; };
  return {
    name: name,
    label: sym,
    keyboardShortcuts: keys,
    action: function() { this.op = f; }
  };
}

function makeUnaryOp(name, f, opt_label) {
  return {
    name: name,
    label: opt_label || name,
    action: function() { this.a2 = f(this.a2); }
  };
}

/** Make a 0-9 Number Action. **/
function makeNum(n) {
  return {
    name: n.toString(),
    keyboardShortcuts: [48+n /* 0 */ , 96+n /* keypad-0 */],
    action: function() { this.a2 = this.a2 == 0 ? n : this.a2.toString() + n; }
  };
}

var DEFAULT_OP = function(a1) { return a1; };
DEFAULT_OP.toString = function() { return ''; };

/** A subclass of FloatFieldView which doesn't display 0 values. **/
FOAModel({
  name:  'CalcFloatFieldView',
  extendsModel: 'FloatFieldView',
  methods: { valueToText: function(v) { return v == 0 ? '' : v.toString(); } }
});

FOAModel({ name: 'History', properties: [ 'op', 'a2' ] });

FOAModel({
  name: 'Calc',

  properties: [
    { name: 'a1', defaultValue: '0' },
    { name: 'a2', defaultValue: 0 },
    {
      name: 'op',
      preSet: function(oldOp, newOp) {
        if ( newOp !== DEFAULT_OP && oldOp !== DEFAULT_OP ) {
          var a3 = this.op(parseFloat(this.a1), parseFloat(this.a2));
          this.history.put(History.create(this));
          this.history.put(History.create({a2: a3}));
          this.a1 = a3;
          this.a2 = 0;
        } else if ( this.a2 ) {
          this.history.put(History.create({a2: this.a2}));
          this.a1 = this.a2;
          this.a2 = 0;
        }
        return newOp;
      },
      defaultValue: DEFAULT_OP
    },
    {
      model_: 'StringProperty',
      name: 'row1',
//      postSet: function(o, n) { console.log(o, ' -> ', n); },
      view: 'ALabel'
    },
    {
      name: 'history',
      model_: 'DAOProperty',
      view: { model_: 'DAOListView', rowView: 'HistoryView' },
      factory: function() { return []; }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      Events.dynamic(function() { this.op; this.a2; }.bind(this), function() {
        this.row1 = this.op + ( this.a2 ? ' ' + this.a2 : '' );
      }.bind(this));
    }
  },

  actions: [
    makeNum(1), makeNum(2), makeNum(3),
    makeNum(4), makeNum(5), makeNum(6),
    makeNum(7), makeNum(8), makeNum(9), makeNum(0),
    makeBinaryOp('div',   '\u00F7', [111, 191],         function(a1, a2) { return a1 / a2; }),
    makeBinaryOp('mult',  '\u00D7', [106, 'shift-56'],  function(a1, a2) { return a1 * a2; }),
    makeBinaryOp('plus',  '+',      [107, 'shift-187'], function(a1, a2) { return a1 + a2; }),
    makeBinaryOp('minus', '–',      [109, 189],         function(a1, a2) { return a1 - a2; }),
    makeBinaryOp('pow',   'yⁿ',     [],                 Math.pow),
    {
      name: 'ac',
      label: 'AC',
      help: 'All Clear.',
      keyboardShortcuts: [ 65 /* a */, 67 /* c */ ],
      action: function() { this.op = DEFAULT_OP; this.a1 = 0; this.history = []; }
    },
    {
      name: 'sign',
      label: '+/-',
      keyboardShortcuts: [ 78 /* n */ , 83 /* s */],
      action: function() { this.a2 = - this.a2; }
    },
    {
      name: 'point',
      label: '.',
      keyboardShortcuts: [ 110, 190 ],
      action: function() {
        if ( this.a2.toString().indexOf('.') == -1 ) this.a2 = this.a2 + '.';
      }
    },
    {
      name: 'equals',
      label: '=',
      keyboardShortcuts: [ 187 /* '=' */, 13 /* <enter> */ ],
      action: function() {
        var a1 = this.a1;
        var a2 = this.a2;
        this.a1 = a2;
        this.history.put(History.create(this));
        this.a2 = this.op(parseFloat(a1), parseFloat(a2));
        this.op = DEFAULT_OP;
      }
    },
    {
      name: 'backspace',
      keyboardShortcuts: [ 8 /* backspace */ ],
      action: function() {
        this.a2 = this.a2 == 0 ? this.a2 : this.a2.toString().substring(0, this.a2.length-1);
      }
    },
    {
      name: 'pi',
      label: 'π',
      action: function() { this.a2 = 3.1415926; }
    },
    makeUnaryOp('fact',   function n(a) { var r = 1; while ( a > 0 ) r *= a--; return r; }, 'x!'),
    makeUnaryOp('inv',    function(a) { return 1.0/a; }, '1/x'),
    makeUnaryOp('sin',    Math.sin),
    makeUnaryOp('cos',    Math.cos),
    makeUnaryOp('tan',    Math.tan),
    makeUnaryOp('asin',   Math.asin),
    makeUnaryOp('acos',   Math.acos),
    makeUnaryOp('atan',   Math.atan),
    makeUnaryOp('square', function(a) { return a*a; }, 'x²'),
    makeUnaryOp('root',   Math.sqrt, '√'),
    makeUnaryOp('log',    function(a) { return Math.log(a) / Math.log(10); }),
    makeUnaryOp('ln',     Math.log),
    makeUnaryOp('exp',    Math.exp, 'eⁿ'),
  ]
});

FOAModel({
  name: 'CalcButton',
  extendsModel: 'ActionButtonCView',
  properties: [
    { name: 'color',      defaultValue: 'white' },
    { name: 'background', defaultValue: '#4c4c4c' },
    { name: 'width',      defaultValue: 70 },
    { name: 'height',     defaultValue: 70 },
    { name: 'font',       defaultValue: '24px Roboto' }
  ]
});
X.registerModel(CalcButton, 'ActionButton');

FOAModel({ name: 'HistoryView',          extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'CalcView',             extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'MainButtonsView',      extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'SecondaryButtonsView', extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
<commit_msg>ACalc: button background colour was a little off.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** Make a Binary Action. **/
function makeBinaryOp(name, sym, keys, f) {
  f.toString = function() { return sym; };
  return {
    name: name,
    label: sym,
    keyboardShortcuts: keys,
    action: function() { this.op = f; }
  };
}

function makeUnaryOp(name, f, opt_label) {
  return {
    name: name,
    label: opt_label || name,
    action: function() { this.a2 = f(this.a2); }
  };
}

/** Make a 0-9 Number Action. **/
function makeNum(n) {
  return {
    name: n.toString(),
    keyboardShortcuts: [48+n /* 0 */ , 96+n /* keypad-0 */],
    action: function() { this.a2 = this.a2 == 0 ? n : this.a2.toString() + n; }
  };
}

var DEFAULT_OP = function(a1) { return a1; };
DEFAULT_OP.toString = function() { return ''; };

/** A subclass of FloatFieldView which doesn't display 0 values. **/
FOAModel({
  name:  'CalcFloatFieldView',
  extendsModel: 'FloatFieldView',
  methods: { valueToText: function(v) { return v == 0 ? '' : v.toString(); } }
});

FOAModel({ name: 'History', properties: [ 'op', 'a2' ] });

FOAModel({
  name: 'Calc',

  properties: [
    { name: 'a1', defaultValue: '0' },
    { name: 'a2', defaultValue: 0 },
    {
      name: 'op',
      preSet: function(oldOp, newOp) {
        if ( newOp !== DEFAULT_OP && oldOp !== DEFAULT_OP ) {
          var a3 = this.op(parseFloat(this.a1), parseFloat(this.a2));
          this.history.put(History.create(this));
          this.history.put(History.create({a2: a3}));
          this.a1 = a3;
          this.a2 = 0;
        } else if ( this.a2 ) {
          this.history.put(History.create({a2: this.a2}));
          this.a1 = this.a2;
          this.a2 = 0;
        }
        return newOp;
      },
      defaultValue: DEFAULT_OP
    },
    {
      model_: 'StringProperty',
      name: 'row1',
//      postSet: function(o, n) { console.log(o, ' -> ', n); },
      view: 'ALabel'
    },
    {
      name: 'history',
      model_: 'DAOProperty',
      view: { model_: 'DAOListView', rowView: 'HistoryView' },
      factory: function() { return []; }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();

      Events.dynamic(function() { this.op; this.a2; }.bind(this), function() {
        this.row1 = this.op + ( this.a2 ? ' ' + this.a2 : '' );
      }.bind(this));
    }
  },

  actions: [
    makeNum(1), makeNum(2), makeNum(3),
    makeNum(4), makeNum(5), makeNum(6),
    makeNum(7), makeNum(8), makeNum(9), makeNum(0),
    makeBinaryOp('div',   '\u00F7', [111, 191],         function(a1, a2) { return a1 / a2; }),
    makeBinaryOp('mult',  '\u00D7', [106, 'shift-56'],  function(a1, a2) { return a1 * a2; }),
    makeBinaryOp('plus',  '+',      [107, 'shift-187'], function(a1, a2) { return a1 + a2; }),
    makeBinaryOp('minus', '–',      [109, 189],         function(a1, a2) { return a1 - a2; }),
    makeBinaryOp('pow',   'yⁿ',     [],                 Math.pow),
    {
      name: 'ac',
      label: 'AC',
      help: 'All Clear.',
      keyboardShortcuts: [ 65 /* a */, 67 /* c */ ],
      action: function() { this.op = DEFAULT_OP; this.a1 = 0; this.history = []; }
    },
    {
      name: 'sign',
      label: '+/-',
      keyboardShortcuts: [ 78 /* n */ , 83 /* s */],
      action: function() { this.a2 = - this.a2; }
    },
    {
      name: 'point',
      label: '.',
      keyboardShortcuts: [ 110, 190 ],
      action: function() {
        if ( this.a2.toString().indexOf('.') == -1 ) this.a2 = this.a2 + '.';
      }
    },
    {
      name: 'equals',
      label: '=',
      keyboardShortcuts: [ 187 /* '=' */, 13 /* <enter> */ ],
      action: function() {
        var a1 = this.a1;
        var a2 = this.a2;
        this.a1 = a2;
        this.history.put(History.create(this));
        this.a2 = this.op(parseFloat(a1), parseFloat(a2));
        this.op = DEFAULT_OP;
      }
    },
    {
      name: 'backspace',
      keyboardShortcuts: [ 8 /* backspace */ ],
      action: function() {
        this.a2 = this.a2 == 0 ? this.a2 : this.a2.toString().substring(0, this.a2.length-1);
      }
    },
    {
      name: 'pi',
      label: 'π',
      action: function() { this.a2 = 3.1415926; }
    },
    makeUnaryOp('fact',   function n(a) { var r = 1; while ( a > 0 ) r *= a--; return r; }, 'x!'),
    makeUnaryOp('inv',    function(a) { return 1.0/a; }, '1/x'),
    makeUnaryOp('sin',    Math.sin),
    makeUnaryOp('cos',    Math.cos),
    makeUnaryOp('tan',    Math.tan),
    makeUnaryOp('asin',   Math.asin),
    makeUnaryOp('acos',   Math.acos),
    makeUnaryOp('atan',   Math.atan),
    makeUnaryOp('square', function(a) { return a*a; }, 'x²'),
    makeUnaryOp('root',   Math.sqrt, '√'),
    makeUnaryOp('log',    function(a) { return Math.log(a) / Math.log(10); }),
    makeUnaryOp('ln',     Math.log),
    makeUnaryOp('exp',    Math.exp, 'eⁿ'),
  ]
});

FOAModel({
  name: 'CalcButton',
  extendsModel: 'ActionButtonCView',
  properties: [
    { name: 'color',      defaultValue: 'white' },
    { name: 'background', defaultValue: '#4b4b4b' },
    { name: 'width',      defaultValue: 70 },
    { name: 'height',     defaultValue: 70 },
    { name: 'font',       defaultValue: '24px Roboto' }
  ]
});
X.registerModel(CalcButton, 'ActionButton');

FOAModel({ name: 'HistoryView',          extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'CalcView',             extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'MainButtonsView',      extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'SecondaryButtonsView', extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
<|endoftext|>


<commit_before>define(function(require) {

  'use strict';

  var PIXI = require('pixi');
  var PixiView = require('common/pixi/view');
  var Simulation = require('models/simulation');
  var Constants = require('constants');

  var ComponentsStyles = PixiView.extend({

    initialize: function(options) {
      this.model = options.model;
      this.vectorViewModel = options.vectorViewModel;
      this.sumVectorModel = options.sumVectorModel;

      this.vectorXViewModel = options.vectorXViewModel;
      this.vectorYViewModel = options.vectorYViewModel;
      this.vectorXContainer = options.vectorXContainer;
      this.vectorYContainer = options.vectorYContainer;
      this.vectorXView = options.vectorXView;
      this.vectorYView = options.vectorYView;

      this.sumVectorXContainer = options.sumVectorXContainer;
      this.sumVectorXView = options.sumVectorXView;
      this.sumVectorXModel = options.sumVectorXViewModel;

      this.sumVectorYContainer = options.sumVectorYContainer;
      this.sumVectorYView = options.sumVectorYView;
      this.sumVectorYModel = options.sumVectorYViewModel;

      this.listenTo(this.model, 'change:componentStyles', this.showComponentStyles);
      this.listenTo(this.model.vectorCollection, 'add remove', this.showComponentStyles);
      this.listenTo(this.vectorViewModel, 'change:targetY change:targetX', this.showComponentStyles);
    },

    showComponentStyles: function() {
      var canvas = $('.scene-view');
      var xOffset = canvas.height() - Constants.X_OFFSET;
      var yOffset = canvas.width() - Constants.Y_OFFSET;

      this.model.resetOrigins(this.vectorXViewModel);
      this.model.resetOrigins(this.vectorYViewModel);
      this.model.clearComponentLines(this.lines);

      if (this.model.get('componentStyles') == 0) {
        this.vectorXContainer.visible = false;
        this.vectorYContainer.visible = false;
      }
      else {
        this.vectorXContainer.visible = true;
        this.vectorYContainer.visible = true;
      }

      if (this.model.get('componentStyles') == 1) {
        this.vectorViewModel.resetVectors(this.vectorXViewModel);
        this.vectorViewModel.resetVectors(this.vectorYViewModel);
        this.vectorXView.transformFrame.rotation = this.vectorXViewModel.get('rotation');
        this.vectorYView.transformFrame.rotation = this.vectorYViewModel.get('rotation');
      }

      if (this.model.get('componentStyles') == 2) {
        this.vectorYViewModel.set('originX', this.vectorViewModel.get('targetX'));
        this.vectorYViewModel.set('targetX', this.vectorViewModel.get('targetX'));

        this.vectorXViewModel.set('originX', this.vectorViewModel.get('originX'));
        this.vectorXViewModel.set('originY', this.vectorViewModel.get('originY'));
        this.vectorXViewModel.set('targetX', this.vectorViewModel.get('targetX'));
        this.vectorXViewModel.set('targetY', this.vectorViewModel.get('targetY'));
        this.vectorXView.transformFrame.rotation = this.vectorXViewModel.get('rotation');
      }

      if (this.model.get('componentStyles') == 3) {
        this.model.clearComponentLines(this.lines);
        this.vectorXViewModel.set('originX', this.vectorViewModel.get('originX'));
        this.vectorXViewModel.set('originY', xOffset);
        this.vectorXViewModel.set('targetX', this.vectorViewModel.get('targetX'));
        this.vectorXViewModel.set('targetY', xOffset);

        this.vectorYViewModel.set('originX', yOffset);
        this.vectorYViewModel.set('originY', this.vectorViewModel.get('originY'));
        this.vectorYViewModel.set('targetX', yOffset);
        this.vectorYViewModel.set('targetY', this.vectorViewModel.get('targetY'));

        this.drawComponentLines(this.vectorYViewModel, this.vectorViewModel, this.vectorXViewModel);
      }
    },

    drawComponentLines: function(vectorYModel, vectorModel, vectorXModel) {
      this.linesContainer = new PIXI.DisplayObjectContainer();

      var lines = new PIXI.Graphics();
      lines.lineStyle(1, this.model.get('darkOrange'), 1);
      lines.moveTo(vectorYModel.get('originX'), vectorYModel.get('originY'));
      lines.lineTo(vectorModel.get('originX'), vectorModel.get('originY'));
      lines.lineTo(vectorXModel.get('originX'), vectorXModel.get('originY'));
      lines.lineTo(vectorXModel.get('targetX'), vectorXModel.get('targetY'));
      lines.lineTo(vectorModel.get('targetX'), vectorModel.get('targetY'));
      lines.lineTo(vectorYModel.get('targetX'), vectorYModel.get('targetY'));
      this.lines = lines;

      this.linesContainer.addChild(this.lines);
      this.displayObject.addChild(this.linesContainer);
    }
  });

  return ComponentsStyles;

});
<commit_msg>Fixed calculations for Style 1 and Style 2 componenet vectors.<commit_after>define(function(require) {

  'use strict';

  var PIXI = require('pixi');
  var PixiView = require('common/pixi/view');
  var Simulation = require('models/simulation');
  var Constants = require('constants');

  var ComponentsStyles = PixiView.extend({

    initialize: function(options) {
      this.model = options.model;
      this.vectorViewModel = options.vectorViewModel;
      this.sumVectorModel = options.sumVectorModel;

      this.vectorXViewModel = options.vectorXViewModel;
      this.vectorYViewModel = options.vectorYViewModel;
      this.vectorXContainer = options.vectorXContainer;
      this.vectorYContainer = options.vectorYContainer;
      this.vectorXView = options.vectorXView;
      this.vectorYView = options.vectorYView;

      this.sumVectorXContainer = options.sumVectorXContainer;
      this.sumVectorXView = options.sumVectorXView;
      this.sumVectorXModel = options.sumVectorXViewModel;

      this.sumVectorYContainer = options.sumVectorYContainer;
      this.sumVectorYView = options.sumVectorYView;
      this.sumVectorYModel = options.sumVectorYViewModel;

      this.listenTo(this.model, 'change:componentStyles', this.showComponentStyles);
      this.listenTo(this.model.vectorCollection, 'add remove', this.showComponentStyles);
      this.listenTo(this.vectorViewModel, 'change:targetY change:targetX', this.showComponentStyles);
    },

    showComponentStyles: function() {
      var canvas = $('.scene-view');
      var xOffset = canvas.height() - Constants.X_OFFSET;
      var yOffset = canvas.width() - Constants.Y_OFFSET;

      this.model.resetOrigins(this.vectorXViewModel);
      this.model.resetOrigins(this.vectorYViewModel);
      this.model.clearComponentLines(this.lines);

      if (this.model.get('componentStyles') == 0) {
        this.vectorXContainer.visible = false;
        this.vectorYContainer.visible = false;
      }
      else {
        this.vectorXContainer.visible = true;
        this.vectorYContainer.visible = true;
      }

      var dx = this.vectorViewModel.get('targetX') - this.vectorViewModel.get('originX');
      var dy = this.vectorViewModel.get('targetY') - this.vectorViewModel.get('originY');

      if (this.model.get('componentStyles') == 1) {
        this.vectorXViewModel.set('originX', this.vectorViewModel.get('originX'));
        this.vectorXViewModel.set('originY', this.vectorViewModel.get('originY'));
        this.vectorXViewModel.set('targetX', this.vectorViewModel.get('originX') + dx);
        this.vectorXViewModel.set('targetY', this.vectorViewModel.get('originY'));

        this.vectorYViewModel.set('originX', this.vectorViewModel.get('originX'));
        this.vectorYViewModel.set('originY', this.vectorViewModel.get('originY'));
        this.vectorYViewModel.set('targetX', this.vectorViewModel.get('originX'));
        this.vectorYViewModel.set('targetY', this.vectorViewModel.get('originY') + dy);
      }

      if (this.model.get('componentStyles') == 2) {
        this.vectorYViewModel.set('originX', this.vectorViewModel.get('targetX'));
        this.vectorYViewModel.set('targetX', this.vectorViewModel.get('targetX'));

        this.vectorXViewModel.set('originX', this.vectorViewModel.get('originX'));
        this.vectorXViewModel.set('originY', this.vectorViewModel.get('originY'));
        this.vectorXViewModel.set('targetX', this.vectorViewModel.get('originX') + dx);
        this.vectorXViewModel.set('targetY', this.vectorViewModel.get('originY'));
      }

      if (this.model.get('componentStyles') == 3) {
        this.model.clearComponentLines(this.lines);
        this.vectorXViewModel.set('originX', this.vectorViewModel.get('originX'));
        this.vectorXViewModel.set('originY', xOffset);
        this.vectorXViewModel.set('targetX', this.vectorViewModel.get('targetX'));
        this.vectorXViewModel.set('targetY', xOffset);

        this.vectorYViewModel.set('originX', yOffset);
        this.vectorYViewModel.set('originY', this.vectorViewModel.get('originY'));
        this.vectorYViewModel.set('targetX', yOffset);
        this.vectorYViewModel.set('targetY', this.vectorViewModel.get('targetY'));

        this.drawComponentLines(this.vectorYViewModel, this.vectorViewModel, this.vectorXViewModel);
      }
    },

    drawComponentLines: function(vectorYModel, vectorModel, vectorXModel) {
      this.linesContainer = new PIXI.DisplayObjectContainer();

      var lines = new PIXI.Graphics();
      lines.lineStyle(1, this.model.get('darkOrange'), 1);
      lines.moveTo(vectorYModel.get('originX'), vectorYModel.get('originY'));
      lines.lineTo(vectorModel.get('originX'), vectorModel.get('originY'));
      lines.lineTo(vectorXModel.get('originX'), vectorXModel.get('originY'));
      lines.lineTo(vectorXModel.get('targetX'), vectorXModel.get('targetY'));
      lines.lineTo(vectorModel.get('targetX'), vectorModel.get('targetY'));
      lines.lineTo(vectorYModel.get('targetX'), vectorYModel.get('targetY'));
      this.lines = lines;

      this.linesContainer.addChild(this.lines);
      this.displayObject.addChild(this.linesContainer);
    }
  });

  return ComponentsStyles;

});
<|endoftext|>


<commit_before>/**
 * Yieldr JavaScript Tracker
 *
 * This library is being served to clients implementing the Yieldr tracking
 * snippet. On a high level it defines a single JSON-P function responsible for
 * performing actions based on the tracking server response.
 *
 * @author Jasper Spijkstra <jasper.spijkstra@yieldr.com>
 * @author Alex Kalyvitis <alex.kalyvitis@yieldr.com>
 */
(function (window, document, y) {
    'use strict';

    /**
     * Random four letter hash.
     *
     * @return {String}
     */
    function hash() {
        return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
    };

    /**
     * Creates a unique session id.
     *
     * @return {String}
     */
    function session() {
        return hash() + hash() +
            "-" + hash() +
            "-" + hash() +
            "-" + hash() +
            "-" + hash() +
            hash() + hash();
    };

    function merge(merged, objects) {
        for (var i in objects) {
            for (var property in objects[i]) {
                merged[property] = objects[i][property];
            }
        }
        return merged;
    };

    /**
     * Describe an object which is able to manipulate html elements.
     *
     * @type {Object}
     */
    var element = (function () {

        /**
         * Maps element types to functions that can handle their placement.
         *
         * @return {Function}
         */
        function map(type) {
            switch (type) {
            case 'html':
                return this.html;
            case 'image':
                return this.image;
            case 'script':
                return this.script;
            case 'iframe':
                return this.iframe;
            }
            return this.html;
        };

        /**
         * Alias to document.createElement
         *
         * @param  {String} type
         * @return {HTMLElement}
         */
        function image(url) {
            var e = document.createElement('img');
            e.width = 0;
            e.height = 0;
            e.src = url;
            return e;
        };

        /**
         * Creates a <script> tag with specified url as source.
         *
         * @param  {String} url
         * @return {HTMLElement}
         */
        function script(url) {
            var e = document.createElement('script');
            e.type = "text/javascript";
            e.src = url;
            e.async = !0;
            return e;
        };

        /**
         * Creates an <iframe> tag with specified url as source.
         *
         * @param  {String} url
         * @return {HTMLElement}
         */
        function iframe(url) {
            var e = document.createElement('iframe');
            e.frameBorder = 0;
            e.width = 0;
            e.height = 0;
            e.src = url;
            return e;
        };

        /**
         * Creates a <div> tag with specified content as inner HTML.
         *
         * @param  {String} content
         * @return {HTMLElement}
         */
        function html(content) {
            var e = document.createElement('div');
            e.innerHTML = content;
            e.style.display = "none";
            return e;
        };

        return {
            map: map,
            image: image,
            script: script,
            iframe: iframe,
            html: html
        };

    }());

    /**
     * Describe an object which is able to manipulate cookies.
     *
     * @type {Object}
     */
    var cookie = (function () {

        /**
         * Sets a cookie.
         *
         * @param  {String} name
         * @param  {String} value
         * @param  {String} extDays
         * @param  {String} domain
         */
        function set(name, value, extDays, domain) {
            var date = new Date();
            date.setDate(date.getDate() + extDays);
            document.cookie = name + "=" + escape(value) + ";expires=" + date + ";domain=" + domain + ";path=/";
        };

        /**
         * Gets a cookies value.
         *
         * @param  {String} name
         * @return {String}
         */
        function get(name) {
            var cookie = "; " + document.cookie,
                parts = cookie.split("; " + name + "=");
            if (parts.length == 2) {
                return unescape(parts.pop().split(";").shift());
            }
            return '';
        };

        return {
            get: get,
            set: set
        };

    }());

    /**
     * Represents a set data structure for storing history.
     *
     * @return {Object}
     */
    var history = (function () {

        /**
         * Holds the items in the set.
         */
        var items = [];

        /**
         * Adds an item to the set if doesn't already exist.
         *
         * @param {String} item
         */
        function add(item) {
            if (item.length > 0 && items.indexOf(item) === -1) {
                items.push(item);
            }
            return this;
        };

        /**
         * Joins the elements in the set with separator.
         *
         * @param  {String} sep
         * @return {String}
         */
        function join(sep) {
            return items.join(sep);
        }

        /**
         * Splits a string of elements and stores in the set.
         *
         * @param  {String} sep
         */
        function split(str, sep) {
            var elements = str.split(sep);
            for (var i in elements) {
                add(elements[i]);
            }
            return this;
        }

        return {
            add: add,
            join: join,
            split: split
        };
    }());

    /**
     * Describe an object which is able to manipulate a URL.
     *
     * @type {Object}
     */
    var url = (function () {

        /**
         * Extracts the URL query parameters in key value pairs.
         *
         * @param  {String} url
         * @return {Object}
         */
        function parseQuery(url) {
            var split = url.split("?"),
                query = {};
            if (split.length >= 2) {
                var pairs = split[1].split('&');
                for (var i = 0, length = pairs.length; i < length; i++) {
                    var pair = pairs[i].split('=');
                    if (pair.length === 2) {
                        query[pair[0]] = pair[1];
                    }
                }
            }
            return query;
        };

        return {
            parseQuery: parseQuery
        };
    }());

    var referrer = (function () {

        /**
         * Maps url regular expressions to third parties.
         *
         * @type {Object}
         */
        var map = {
            "https?://(.*)criteo.([^/?]*)": "criteo",
            "https?://(.*)doubleclick.([^/?]*)": "doubleclick",
            "https?://(.*)turn.([^/?]*)": "turn",
            "https?://(.*)adnxs.([^/?]*)": "appnexus",
            "https?://(.*)rubiconproject.([^/?]*)": "rubicon",
            "https?://(.*)254a.([^/?]*)": "yieldr",
            "https?://(.*)google.([^/?]*)": "google",
            "https?://(.*)bing.com": "bing",
            "https?://(.*)yahoo.com": "yahoo"
        };

        /**
         * Display third parties.
         *
         * @type {Array}
         */
        var display = ["criteo", "doubleclick", "turn", "appnexus", "rubicon", "yieldr"];

        /**
         * Search engines.
         *
         * @type {Array}
         */
        var search = ["google", "bing", "yahoo"];

        /**
         * Returns the type of a URL.
         *
         * @param  {String} url
         * @return {String}
         */
        function type(url) {
            var n = name(url);
            if (-1 !== display.indexOf(n)) {
                return 'display';
            } else if (-1 !== search.indexOf(n)) {
                return 'search';
            }
            return 'other';
        }

        /**
         * Tries to match a name to a referrer url based on a known list.
         *
         * @param  {String} url
         * @return {String}
         */
        function name(url) {
            for (var regex in map) {
                if (-1 !== url.search(regex)) {
                    return map[regex];
                }
            }
            return clean(url);
        };

        /**
         * Cleans a url.
         *
         * @param  {String} url
         * @return {String}
         */
        function clean(url) {
            return url.replace(/^https?\:\/\//, '').split(/[/?#]/)[0];
        };

        return {
            name: name,
            type: type,
            clean: clean
        }

    }());

    /**
     * Places the piggybacks as given to us by the tracking server.
     *
     * @param  {Object}   response
     */
    y.callback = function (response) {
        if (response.status === 'success') {
            var body = document.getElementsByTagName("body")[0];
            for (var piggybacks in response.data) {
                var func = element.map(piggybacks);
                for (var piggyback in response.data[piggybacks]) {
                    body.appendChild(func(response.data[piggybacks][piggyback]));
                }
            }
        }
    };

    /**
     * Performs an HTTP request to the remote server.
     *
     * @param  {Object} params
     */
    y.fire = function (params) {
        var pairs = [];
        for (var key in params) {
            pairs.push(key + '=' + params[key]);
        }
        var element = document.createElement('script');
        element.src = ((document.location.protocol === 'https:') ? 'https:' : 'http:') + '//y.254a.com/pixel' + '?' + pairs.join('&');
        document.body.insertBefore(element);
    };

    /**
     * Initiates tracking.
     *
     * @param  {Object} data
     */
    y.track = function () {
        var sessionStorage = window.sessionStorage,
            sessid = sessionStorage.getItem('ysess'),
            location = document.location,
            path = location.pathname,
            host = location.hostname,
            href = location.href,
            ref = document.referrer || 'NO_REFERRER',
            data = y.data,
            trafficSource = referrer.name(ref),
            trafficType = referrer.type(trafficSource),
            params = url.parseQuery(href);

        if (trafficSource === host) {
            trafficType = 'self';
        }

        cookie.set('yhist', history.split(cookie.get('yhist'), '|').add(trafficSource).join('|'), 30, host);

        if (!sessid) {
            sessid = session();
            sessionStorage.setItem('ysess', sessid);
            sessionStorage.setItem('ytsrc', trafficSource);
            sessionStorage.setItem('ytt', trafficType);
        } else {
            trafficSource = sessionStorage.getItem('ytsrc');
            trafficType = sessionStorage.getItem('ytt');
        }

        merge(data, [params]);

        data.sessid = sessid;
        data.referrer = escape(host + path);
        data.path = escape(path);
        data.prev = referrer.clean(ref);
        data.traffic_source = trafficSource;
        data.traffic_type = trafficType;
        data.traffic_history = cookie.get('yhist');

        if (y.debug) {
            console.log("q", data);
        }

        y.fire(data);
    };

    y.track(); // Start tracking
})(window, document, window.y);<commit_msg>placing arbitrary scripts is now possible using iframe<commit_after>/**
 * Yieldr JavaScript Tracker
 *
 * This library is being served to clients implementing the Yieldr tracking
 * snippet. On a high level it defines a single JSON-P function responsible for
 * performing actions based on the tracking server response.
 *
 * @author Jasper Spijkstra <jasper.spijkstra@yieldr.com>
 * @author Alex Kalyvitis <alex.kalyvitis@yieldr.com>
 */
(function (window, document, y) {
    'use strict';

    /**
     * Random four letter hash.
     *
     * @return {String}
     */
    function hash() {
        return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
    };

    /**
     * Creates a unique session id.
     *
     * @return {String}
     */
    function session() {
        return hash() + hash() +
            "-" + hash() +
            "-" + hash() +
            "-" + hash() +
            "-" + hash() +
            hash() + hash();
    };

    function merge(merged, objects) {
        for (var i in objects) {
            for (var property in objects[i]) {
                merged[property] = objects[i][property];
            }
        }
        return merged;
    };

    /**
     * Describe an object which is able to manipulate html elements.
     *
     * @type {Object}
     */
    var element = (function () {

        /**
         * Maps element types to functions that can handle their placement.
         *
         * @return {Function}
         */
        function map(type) {
            switch (type) {
            case 'html':
                return this.html;
            case 'image':
                return this.image;
            case 'script':
                return this.script;
            case 'iframe':
                return this.iframe;
            }
            return null;
        };

        /**
         * Alias to document.createElement
         *
         * @param  {String} type
         * @return {HTMLElement}
         */
        function image(url) {
            var e = document.createElement('img');
            e.width = 0;
            e.height = 0;
            e.src = url;
            return e;
        };

        /**
         * Creates a <script> tag with specified url as source.
         *
         * @param  {String} url
         * @return {HTMLElement}
         */
        function script(url) {
            var e = document.createElement('script');
            e.type = "text/javascript";
            e.src = url;
            e.async = !0;
            return e;
        };

        /**
         * Creates an <iframe> tag with specified url as source.
         *
         * @param  {String} url
         * @return {HTMLElement}
         */
        function iframe(url) {
            var e = document.createElement('iframe');
            e.frameBorder = 0;
            e.width = 0;
            e.height = 0;
            e.src = url;
            return e;
        };

        /**
         * Creates an <iframe> tag with specified content.
         *
         * @param  {String} content
         * @return {HTMLElement}
         */
        function html(content) {
            var e = iframe("");
            document.body.appendChild(e);
            var d = e.contentWindow.document;
            d.open();
            d.write(content);
            d.close();
            return e;
        };

        return {
            map: map,
            image: image,
            script: script,
            iframe: iframe,
            html: html
        };

    }());

    /**
     * Describe an object which is able to manipulate cookies.
     *
     * @type {Object}
     */
    var cookie = (function () {

        /**
         * Sets a cookie.
         *
         * @param  {String} name
         * @param  {String} value
         * @param  {String} extDays
         * @param  {String} domain
         */
        function set(name, value, extDays, domain) {
            var date = new Date();
            date.setDate(date.getDate() + extDays);
            document.cookie = name + "=" + escape(value) + ";expires=" + date + ";domain=" + domain + ";path=/";
        };

        /**
         * Gets a cookies value.
         *
         * @param  {String} name
         * @return {String}
         */
        function get(name) {
            var cookie = "; " + document.cookie,
                parts = cookie.split("; " + name + "=");
            if (parts.length == 2) {
                return unescape(parts.pop().split(";").shift());
            }
            return '';
        };

        return {
            get: get,
            set: set
        };

    }());

    /**
     * Represents a set data structure for storing history.
     *
     * @return {Object}
     */
    var history = (function () {

        /**
         * Holds the items in the set.
         */
        var items = [];

        /**
         * Adds an item to the set if doesn't already exist.
         *
         * @param {String} item
         */
        function add(item) {
            if (item.length > 0 && items.indexOf(item) === -1) {
                items.push(item);
            }
            return this;
        };

        /**
         * Joins the elements in the set with separator.
         *
         * @param  {String} sep
         * @return {String}
         */
        function join(sep) {
            return items.join(sep);
        }

        /**
         * Splits a string of elements and stores in the set.
         *
         * @param  {String} sep
         */
        function split(str, sep) {
            var elements = str.split(sep);
            for (var i in elements) {
                add(elements[i]);
            }
            return this;
        }

        return {
            add: add,
            join: join,
            split: split
        };
    }());

    /**
     * Describe an object which is able to manipulate a URL.
     *
     * @type {Object}
     */
    var url = (function () {

        /**
         * Extracts the URL query parameters in key value pairs.
         *
         * @param  {String} url
         * @return {Object}
         */
        function parseQuery(url) {
            var split = url.split("?"),
                query = {};
            if (split.length >= 2) {
                var pairs = split[1].split('&');
                for (var i = 0, length = pairs.length; i < length; i++) {
                    var pair = pairs[i].split('=');
                    if (pair.length === 2) {
                        query[pair[0]] = pair[1];
                    }
                }
            }
            return query;
        };

        return {
            parseQuery: parseQuery
        };
    }());

    var referrer = (function () {

        /**
         * Maps url regular expressions to third parties.
         *
         * @type {Object}
         */
        var map = {
            "https?://(.*)criteo.([^/?]*)": "criteo",
            "https?://(.*)doubleclick.([^/?]*)": "doubleclick",
            "https?://(.*)turn.([^/?]*)": "turn",
            "https?://(.*)adnxs.([^/?]*)": "appnexus",
            "https?://(.*)rubiconproject.([^/?]*)": "rubicon",
            "https?://(.*)254a.([^/?]*)": "yieldr",
            "https?://(.*)google.([^/?]*)": "google",
            "https?://(.*)bing.com": "bing",
            "https?://(.*)yahoo.com": "yahoo"
        };

        /**
         * Display third parties.
         *
         * @type {Array}
         */
        var display = ["criteo", "doubleclick", "turn", "appnexus", "rubicon", "yieldr"];

        /**
         * Search engines.
         *
         * @type {Array}
         */
        var search = ["google", "bing", "yahoo"];

        /**
         * Returns the type of a URL.
         *
         * @param  {String} url
         * @return {String}
         */
        function type(url) {
            var n = name(url);
            if (-1 !== display.indexOf(n)) {
                return 'display';
            } else if (-1 !== search.indexOf(n)) {
                return 'search';
            }
            return 'other';
        }

        /**
         * Tries to match a name to a referrer url based on a known list.
         *
         * @param  {String} url
         * @return {String}
         */
        function name(url) {
            for (var regex in map) {
                if (-1 !== url.search(regex)) {
                    return map[regex];
                }
            }
            return clean(url);
        };

        /**
         * Cleans a url.
         *
         * @param  {String} url
         * @return {String}
         */
        function clean(url) {
            return url.replace(/^https?\:\/\//, '').split(/[/?#]/)[0];
        };

        return {
            name: name,
            type: type,
            clean: clean
        }

    }());

    /**
     * Places the piggybacks as given to us by the tracking server.
     *
     * @param  {Object}   response
     */
    y.callback = function (response) {
        if (response.status === 'success') {
            var body = document.getElementsByTagName("body")[0];
            for (var piggybacks in response.data) {
                var func = element.map(piggybacks);
                for (var piggyback in response.data[piggybacks]) {
                    if (func) {
                        body.appendChild(func(response.data[piggybacks][piggyback]));
                    }
                }
            }
        }
    };

    /**
     * Performs an HTTP request to the remote server.
     *
     * @param  {Object} params
     */
    y.fire = function (params) {
        var pairs = [];
        for (var key in params) {
            pairs.push(key + '=' + params[key]);
        }
        var element = document.createElement('script');
        element.src = ((document.location.protocol === 'https:') ? 'https:' : 'http:') + '//y.254a.com/pixel' + '?' + pairs.join('&');
        document.body.insertBefore(element);
    };

    /**
     * Initiates tracking.
     *
     * @param  {Object} data
     */
    y.track = function () {
        var sessionStorage = window.sessionStorage,
            sessid = sessionStorage.getItem('ysess'),
            location = document.location,
            path = location.pathname,
            host = location.hostname,
            href = location.href,
            ref = document.referrer || 'NO_REFERRER',
            data = y.data,
            trafficSource = referrer.name(ref),
            trafficType = referrer.type(trafficSource),
            params = url.parseQuery(href);

        if (trafficSource === host) {
            trafficType = 'self';
        }

        cookie.set('yhist', history.split(cookie.get('yhist'), '|').add(trafficSource).join('|'), 30, host);

        if (!sessid) {
            sessid = session();
            sessionStorage.setItem('ysess', sessid);
            sessionStorage.setItem('ytsrc', trafficSource);
            sessionStorage.setItem('ytt', trafficType);
        } else {
            trafficSource = sessionStorage.getItem('ytsrc');
            trafficType = sessionStorage.getItem('ytt');
        }

        merge(data, [params]);

        data.sessid = sessid;
        data.referrer = escape(host + path);
        data.path = escape(path);
        data.prev = referrer.clean(ref);
        data.traffic_source = trafficSource;
        data.traffic_type = trafficType;
        data.traffic_history = cookie.get('yhist');

        if (y.debug) {
            console.log("q", data);
        }

        y.fire(data);
    };

    y.track(); // Start tracking
})(window, document, window.y);<|endoftext|>