Raw Source
eliran123456 / Shockr - Tiberium Alliances Tools

// ==UserScript==
// @name            Shockr - Tiberium Alliances Tools
// @author          Shockr <shockr@c.ac.nz>
// @description     Tools to work with Tiberium alliances http://c.ac.nz/taopt
// @include         http*://prodgame*.alliances.commandandconquer.com/*/index.aspx*
// @grant           GM_xmlhttpRequest
// @grant           GM_updatingEnabled
// @grant           unsafeWindow
// @version         4.5.3
// @downloadURL     https://c.ac.nz/client/shockrtools.user.js
// @icon            https://c.ac.nz/favicon.png
// ==/UserScript==

var setupShockrTools = function() {
/* Begin: client/modules/main.js */
var ST = window.ST || {};
ST.modules = [];

ST.register = function(module) {
    ST[module.name.toLowerCase()] = module;
    ST[module.name] = module;

    for (var i = 0; i < ST.modules.length; i++) {
        var stMod = ST.modules[i];
        if (stMod.name === module.name) {
            ST.log.info('Destroy [' + module.name + ']');
            if (typeof stMod.destroy === 'function') {
                stMod.destroy();
            }
            ST.modules.slice(i, 1);
            break;
        }
    }

    ST.modules.push(module);

    if (ST.util.isLoaded() === false) {
        return;
    }
    if (typeof module.startup === 'function') {
        module.startup();
    }
};

ST.startup = function() {
    if (ST.config === undefined) {
        ST.config = ST.storage.get('config') || {};
    }

    if (ST.util.isLoaded() === false) {
        setTimeout(function() {
            ST.startup();
        }, 1000);
        return;
    }

    ST.modules.forEach(function(o) {
        if (ST.config[o.name.toLowerCase()] === false) {
            return ST.log.info('Skipping [' + o.name + '] as its disabled');
        }

        ST.log.info('Starting [' + o.name + ']');
        if (typeof o.startup === 'function') {
            o.startup();
        }
    });
};

ST.log = function() {
    console.log.apply(console, arguments);
};
ST.log.WARN = 40;
ST.log.INFO = 30;
ST.log.DEBUG = 20;
ST.log.level = ST.log.WARN;

ST.log.info = function() {
    if (ST.log.level > ST.log.INFO) {
        return;
    }
    ST.log(arguments);
};

ST.log.warn = function() {
    if (ST.log.level > ST.log.WARN) {
        return;
    }

    ST.log(arguments);
};

ST.log.debug = function() {
    if (ST.log.level > ST.log.DEBUG) {
        return;
    }

    ST.log(arguments);
};

// Wrap localStorage
// - Prefix keys with "ST:"
// - Automatically convert to/from JSON
// - Remove item if setting null or undefined
ST.storage = {};
ST.storage.get = function(key) {
    var value = localStorage.getItem('ST:' + key);
    if (value === null) {
        return value;
    }
    return JSON.parse(value);
};

ST.storage.set = function(key, value) {
    if (value === null || value === undefined) {
        return ST.storage.remove(key);
    }
    if (typeof value !== 'string') {
        value = JSON.stringify(value);
    }

    return localStorage.setItem('ST:' + key, value);
};

ST.storage.remove = function(key) {
    return localStorage.removeItem('ST:' + key);
};


ST.util = {
    // URL: 'wss://localhost:443/websocket',
    URL: 'wss://c.ac.nz/websocket',

    isLoaded: function() {
        if (typeof qx === 'undefined') {
            return false;
        }

        var a = qx.core.Init.getApplication();
        if (a === null || a === undefined) {
            return false;
        }

        var mb = qx.core.Init.getApplication().getMenuBar();
        if (mb === null || mb === undefined) {
            return false;
        }

        var md = ClientLib.Data.MainData.GetInstance();
        if (md === null || md === undefined) {
            return false;
        }

        var player = md.get_Player();
        if (player === null || player === undefined) {
            return false;
        }

        if (player.get_Name() === '') {
            return false;
        }

        return true;
    },

    connect: function(callback) {
        var options = {
            endpoint: ST.util.URL,
            SocketConstructor: WebSocket
        };

        var ddp = new DDP(options);

        ddp.on('connected', function() {
            ST.log.info('Websocket connected');
            ST.util.ddp = ddp;
            if (typeof callback === 'function') {
                callback();
            }
        });
    },

    api: function(method, data, callback) {
        if (typeof callback === 'undefined') {
            callback = ST.util.noop;
        }

        if (ST.util.ddp === undefined) {
            return ST.util.connect(function() {
                ST.util.api(method, data, callback);
            });
        }

        ST.util.ddp.method(method, [data], callback);
    },

    alert: function(message) {
        window.alert(message);
    },

    handleError: function() {
        console.log('Error Caught:', arguments);
    },

    noop: function() {},

    _g: function(k, r, q, m) {
        var p = [];
        var o = k.toString();
        var n = o.replace(/\s/gim, '');
        p = n.match(r);
        var l;
        for (l = 1; l < (m + 1); l++) {
            if (p !== null && p[l].length === 6) {
                console.debug(q, l, p[l]);
            } else {
                if (p !== null && p[l].length > 0) {
                    console.warn(q, l, p[l]);
                } else {
                    console.error('Error - ', q, l, 'not found');
                    console.warn(q, n);
                }
            }
        }
        return p;
    },

    // Empty button until one gets made for us.
    button: {
        setLabel: function() {}
    }
};

window.ST = ST;

ST.startup();

window.onerror = function() {
    ST.util.handleError(arguments);
};/* End: client/modules/main.js */
};


var ST_MODULES = window.ST_MODULES = [];
ST_MODULES.push(setupShockrTools);

var setupShockrModules = function() {
    console.time('ST:LoadModules');
/* Begin: client/modules/ddp.js */
(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.DDP = factory();
    }
}(this, function() {

    'use strict';

    var uniqueId = (function() {
        var i = 0;
        return function() {
            return (i++).toString();
        };
    })();

    var INIT_DDP_MESSAGE = '{\"server_id\":\"0\"}';
    // After hitting the plateau, it'll try to reconnect
    // every 16.5 seconds
    var RECONNECT_ATTEMPTS_BEFORE_PLATEAU = 10;
    var TIMER_INCREMENT = 300;
    var DEFAULT_PING_INTERVAL = 10000;
    var DDP_SERVER_MESSAGES = [
        'added', 'changed', 'connected', 'error', 'failed',
        'nosub', 'ready', 'removed', 'result', 'updated',
        'ping', 'pong'
    ];
    var DDP_VERSION = '1';

    var DDP = function(options) {
        // Configuration
        this._endpoint = options.endpoint;
        this._SocketConstructor = options.SocketConstructor;
        this._autoreconnect = !options.do_not_autoreconnect;
        this._ping_interval = options._ping_interval || DEFAULT_PING_INTERVAL;
        this._socketInterceptFunction = options.socketInterceptFunction;
        // Subscriptions callbacks
        this._onReadyCallbacks = {};
        this._onStopCallbacks = {};
        this._onErrorCallbacks = {};
        // Methods callbacks
        this._onResultCallbacks = {};
        this._onUpdatedCallbacks = {};
        this._events = {};
        this._queue = [];
        // Setup
        this.readyState = -1;
        this._reconnect_count = 0;
        this._reconnect_incremental_timer = 0;
        // Init
        if (!options.do_not_autoconnect) {
            this.connect();
        }
    };
    DDP.prototype.constructor = DDP;

    DDP.prototype.connect = function() {
        this.readyState = 0;
        this._socket = new this._SocketConstructor(this._endpoint);
        this._socket.onopen = this._on_socket_open.bind(this);
        this._socket.onmessage = this._on_socket_message.bind(this);
        this._socket.onerror = this._on_socket_error.bind(this);
        this._socket.onclose = this._on_socket_close.bind(this);
    };

    DDP.prototype.method = function(name, params, onResult, onUpdated) {
        var id = uniqueId();
        this._onResultCallbacks[id] = onResult;
        this._onUpdatedCallbacks[id] = onUpdated;
        this._send({
            msg: 'method',
            id: id,
            method: name,
            params: params
        });
        return id;
    };

    DDP.prototype.sub = function(name, params, onReady, onStop, onError) {
        var id = uniqueId();
        this._onReadyCallbacks[id] = onReady;
        this._onStopCallbacks[id] = onStop;
        this._onErrorCallbacks[id] = onError;
        this._send({
            msg: 'sub',
            id: id,
            name: name,
            params: params
        });
        return id;
    };

    DDP.prototype.unsub = function(id) {
        this._send({
            msg: 'unsub',
            id: id
        });
        return id;
    };

    DDP.prototype.on = function(name, handler) {
        this._events[name] = this._events[name] || [];
        this._events[name].push(handler);
    };

    DDP.prototype.off = function(name, handler) {
        if (!this._events[name]) {
            return;
        }
        var index = this._events[name].indexOf(handler);
        if (index !== -1) {
            this._events[name].splice(index, 1);
        }
    };

    DDP.prototype._emit = function(name /* , arguments */ ) {
        if (!this._events[name]) {
            return;
        }
        var args = arguments;
        var self = this;
        this._events[name].forEach(function(handler) {
            handler.apply(self, Array.prototype.slice.call(args, 1));
        });
    };

    DDP.prototype._send = function(object) {
        if (this.readyState !== 1 && object.msg !== 'connect') {
            this._queue.push(object);
            return;
        }
        var message;
        if (typeof EJSON === 'undefined') {
            message = JSON.stringify(object);
        } else {
            message = EJSON.stringify(object);
        }
        if (this._socketInterceptFunction) {
            this._socketInterceptFunction({
                type: 'socket_message_sent',
                message: message,
                timestamp: Date.now()
            });
        }
        this._socket.send(message);
    };

    DDP.prototype._try_reconnect = function() {
        if (this._reconnect_count < RECONNECT_ATTEMPTS_BEFORE_PLATEAU) {
            setTimeout(this.connect.bind(this), this._reconnect_incremental_timer);
            this._reconnect_count += 1;
            this._reconnect_incremental_timer += TIMER_INCREMENT * this._reconnect_count;
        } else {
            setTimeout(this.connect.bind(this), this._reconnect_incremental_timer);
        }
    };

    DDP.prototype._on_result = function(data) {
        if (this._onResultCallbacks[data.id]) {
            this._onResultCallbacks[data.id](data.error, data.result);
            delete this._onResultCallbacks[data.id];
            if (data.error) {
                delete this._onUpdatedCallbacks[data.id];
            }
        } else {
            if (data.error) {
                delete this._onUpdatedCallbacks[data.id];
                throw data.error;
            }
        }
    };
    DDP.prototype._on_updated = function(data) {
        var self = this;
        data.methods.forEach(function(id) {
            if (self._onUpdatedCallbacks[id]) {
                self._onUpdatedCallbacks[id]();
                delete self._onUpdatedCallbacks[id];
            }
        });
    };
    DDP.prototype._on_nosub = function(data) {
        if (data.error) {
            if (!this._onErrorCallbacks[data.id]) {
                delete this._onReadyCallbacks[data.id];
                delete this._onStopCallbacks[data.id];
                throw new Error(data.error);
            }
            this._onErrorCallbacks[data.id](data.error);
            delete this._onReadyCallbacks[data.id];
            delete this._onStopCallbacks[data.id];
            delete this._onErrorCallbacks[data.id];
            return;
        }
        if (this._onStopCallbacks[data.id]) {
            this._onStopCallbacks[data.id]();
        }
        delete this._onReadyCallbacks[data.id];
        delete this._onStopCallbacks[data.id];
        delete this._onErrorCallbacks[data.id];
    };
    DDP.prototype._on_ready = function(data) {
        var self = this;
        data.subs.forEach(function(id) {
            if (self._onReadyCallbacks[id]) {
                self._onReadyCallbacks[id]();
                delete self._onReadyCallbacks[id];
            }
        });
    };

    DDP.prototype._on_error = function(data) {
        this._emit('error', data);
    };
    DDP.prototype._on_connected = function(data) {
        var self = this;
        var firstCon = self._reconnect_count === 0;
        var eventName = firstCon ? 'connected' : 'reconnected';
        self.readyState = 1;
        self._reconnect_count = 0;
        self._reconnect_incremental_timer = 0;
        var length = self._queue.length;
        for (var i = 0; i < length; i++) {
            self._send(self._queue.shift());
        }
        self._emit(eventName, data);
        // Set up keepalive ping-s
        self._ping_interval_handle = setInterval(function() {
            var id = uniqueId();
            self._send({
                msg: 'ping',
                id: id
            });
        }, self._ping_interval);
    };
    DDP.prototype._on_failed = function(data) {
        this.readyState = 4;
        this._emit('failed', data);
    };
    DDP.prototype._on_added = function(data) {
        this._emit('added', data);
    };
    DDP.prototype._on_removed = function(data) {
        this._emit('removed', data);
    };
    DDP.prototype._on_changed = function(data) {
        this._emit('changed', data);
    };
    DDP.prototype._on_ping = function(data) {
        this._send({
            msg: 'pong',
            id: data.id
        });
    };
    DDP.prototype._on_pong = function() {
        // For now, do nothing.
        // In the future we might want to log latency or so.
    };

    DDP.prototype._on_socket_close = function() {
        if (this._socketInterceptFunction) {
            this._socketInterceptFunction({
                type: 'socket_close',
                timestamp: Date.now()
            });
        }
        clearInterval(this._ping_interval_handle);
        this.readyState = 4;
        this._emit('socket_close');
        if (this._autoreconnect) {
            this._try_reconnect();
        }
    };
    DDP.prototype._on_socket_error = function(e) {
        if (this._socketInterceptFunction) {
            this._socketInterceptFunction({
                type: 'socket_error',
                error: JSON.stringify(e),
                timestamp: Date.now()
            });
        }
        clearInterval(this._ping_interval_handle);
        this.readyState = 4;
        this._emit('socket_error', e);
    };
    DDP.prototype._on_socket_open = function() {
        if (this._socketInterceptFunction) {
            this._socketInterceptFunction({
                type: 'socket_open',
                timestamp: Date.now()
            });
        }
        this._send({
            msg: 'connect',
            version: DDP_VERSION,
            support: [DDP_VERSION]
        });
    };
    DDP.prototype._on_socket_message = function(message) {
        if (this._socketInterceptFunction) {
            this._socketInterceptFunction({
                type: 'socket_message_received',
                message: message.data,
                timestamp: Date.now()
            });
        }
        var data;
        if (message.data === INIT_DDP_MESSAGE) {
            return;
        }
        try {
            if (typeof EJSON === 'undefined') {
                data = JSON.parse(message.data);
            } else {
                data = EJSON.parse(message.data);
            }
            if (DDP_SERVER_MESSAGES.indexOf(data.msg) === -1) {
                throw new Error();
            }
        } catch (e) {
            console.warn('Non DDP message received:', INIT_DDP_MESSAGE);
            console.warn(message.data);
            return;
        }
        this['_on_' + data.msg](data);
    };

    return DDP;

}));/* End: client/modules/ddp.js */
/* Begin: client/modules/button.js */
/* globals ClientLib, qx, ST */

var Button = {
    name: 'Button',

    startup: function() {
        registerButtons();
        // Init the button
        ST.qx.main.getInstance();

        ST.util.button.setLabel('Scan');
    }
};

function registerButtons() {
    qx.Class.define('ST.qx.main', {
        type: 'singleton',
        extend: qx.core.Object,
        construct: function() {
            this.button = new qx.ui.form.Button('Scan');
            this.button.set({
                width: 100,
                appearance: 'button-bar-center',
                toolTipText: 'Scan'
            });

            ST.util.button = this.button;
            this.button.addListener('click', this.click, this);
            var mainBar = qx.core.Init.getApplication().getUIItem(ClientLib.Data.Missions.PATH.BAR_MENU);
            mainBar.getChildren()[1].addAt(this.button, 8, {
                top: 0,
                right: 0
            });
            console.log('ST:Util - Scan Button added');
        },
        members: {

            click: function() {
                if (ST.BaseScanner.isScanning()) {
                    return ST.BaseScanner.abort();
                }
                ST.BaseScanner.scan();
            }
        }

    });
}

ST.register(Button);/* End: client/modules/button.js */
/* Begin: client/modules/basescanner.js */
/* globals ClientLib, phe, ST */
var MAX_FAILS = 25;

var BaseScanner = {
    name: 'BaseScanner',

    _patched: false,
    _bases: {},
    _selectionBases: {},
    _toScan: [],
    _scanning: false,
    failCount: 0,

    scan: function() {
        if (BaseScanner._scanning) {
            return;
        }

        window.open('https://c.ac.nz/#/bases/' + ClientLib.Data.MainData.GetInstance().get_Player().get_Name(), 'ST-BaseScanner');
        ST.util.button.setLabel('Scanning');
        BaseScanner._bases = {};

        BaseScanner._scanning = true;
        BaseScanner._count = 0;
        BaseScanner._done = 0;
        BaseScanner.index = -1;
        BaseScanner._toScanMap = {};
        BaseScanner._toScan = [];

        var allCities = ClientLib.Data.MainData.GetInstance().get_Cities().get_AllCities().d;
        for (var selectedBaseID in allCities) {
            if (!allCities.hasOwnProperty(selectedBaseID)) {
                continue;
            }

            var selectedBase = allCities[selectedBaseID];
            if (selectedBase === undefined) {
                throw new Error('unable to find base: ' + selectedBaseID);
            }

            BaseScanner.getNearByBases(selectedBase);
        }

        BaseScanner.scanNextBase();
    },

    getNearByBases: function(base) {
        var x = base.get_PosX();
        var y = base.get_PosY();

        var maxAttack = ClientLib.Data.MainData.GetInstance().get_Server().get_MaxAttackDistance() - 0.5;
        var world = ClientLib.Data.MainData.GetInstance().get_World();
        ST.log.debug('[BaseScanner] Scanning from ' + x + ':' + y);
        var toScanCount = 0;
        for (var scanY = y - 11; scanY <= y + 11; scanY++) {
            for (var scanX = x - 11; scanX <= x + 11; scanX++) {
                var distX = Math.abs(x - scanX);
                var distY = Math.abs(y - scanY);
                var distance = Math.sqrt((distX * distX) + (distY * distY));
                // too far away to scan
                if (distance >= maxAttack) {
                    continue;
                }
                // already scanning this base from another city.
                if (BaseScanner._toScanMap[scanX + ':' + scanY] !== undefined ||
                    BaseScanner._bases[scanX + ':' + scanY] !== undefined) {
                    continue;
                }

                var object = world.GetObjectFromPosition(scanX, scanY);
                // Nothing to scan
                if (object === null) {
                    continue;
                }


                // Object isnt a NPC Base/Camp/Outpost
                if (object.Type !== ClientLib.Data.WorldSector.ObjectType.NPCBase && object.Type !== ClientLib.Data.WorldSector.ObjectType.NPCCamp) {
                    continue;
                }

                if (typeof object.getCampType === 'function' && object.getCampType() === ClientLib.Data.Reports.ENPCCampType.Destroyed) {
                    continue;
                }

                // Cached
                var offlineBase = BaseScanner.getOfflineBase(scanX, scanY);
                if (offlineBase !== null && offlineBase.id === object.getID()) {
                    delete offlineBase.obj;
                    BaseScanner._bases[scanX + ':' + scanY] = offlineBase;
                    var data = {
                        'base': offlineBase,
                        'world': ClientLib.Data.MainData.GetInstance().get_Server().get_WorldId(),
                        'player': ClientLib.Data.MainData.GetInstance().get_Player().get_Name(),
                        'alliance': ClientLib.Data.MainData.GetInstance().get_Alliance().get_Id()
                    };
                    BaseScanner._count ++;
                    ST.util.api('scanBase', data, BaseScanner.done);
                    continue;
                }

                var scanBase = {
                    x: scanX,
                    y: scanY,
                    level: object.getLevel(),
                    id: object.getID(),
                    distance: distance,
                    selectedBaseID: base.get_Id(),
                    alliance: ClientLib.Data.MainData.GetInstance().get_Alliance().get_Id(),
                    failCount: 0
                };

                BaseScanner._toScan.push(scanBase);
                BaseScanner._toScanMap[scanX + ':' + scanY] = scanBase;
                toScanCount++;
            }
        }

        ST.log.info('[BaseScanner] Found ' + toScanCount + ' new bases to scan from:' + base.get_Name());

    },

    abort: function() {
        BaseScanner._scanning = false;
        BaseScanner._abort = true;
    },

    done: function() {
        BaseScanner._done ++;
        if (BaseScanner._scanning === false &&
            BaseScanner._count === BaseScanner._done) {
            ST.util.button.setLabel('Done! (' + BaseScanner._count + ')');
            setTimeout(function(){
                ST.util.button.setLabel('Scan');
            }, 2000);
        } else if (BaseScanner._scanning === false) {
            ST.util.button.setLabel('Scan');
        } else {
            ST.util.button.setLabel('Scanning... (' + BaseScanner._count + '/'  + BaseScanner._toScan.length + ')');
        }
    },

    getBaseLayout: function(base) {
        if (BaseScanner._abort) {
            BaseScanner._abort = false;
            BaseScanner._scanning = false;
            return ST.log.info('[BaseScanner] aborting');
        }

        if (base === undefined) {
            BaseScanner._abort = false;
            BaseScanner._scanning = false;
            BaseScanner.done();
            return;
        }

        if (BaseScanner._lastBaseID !== base.selectedBaseID) {
            BaseScanner.setCurrentBase(base.selectedBaseID);
        }

        // var currentCity = ClientLib.Data.MainData.GetInstance().get_Cities().get_CurrentOwnCity();
        // var world = ClientLib.Data.MainData.GetInstance().get_World();
        ClientLib.Data.MainData.GetInstance().get_Cities().set_CurrentCityId(base.id);
        var scanBase = ClientLib.Data.MainData.GetInstance().get_Cities().GetCity(base.id);

        var comm = ClientLib.Net.CommunicationManager.GetInstance();
        comm.UserAction();

        // base was destroyed.
        if (scanBase.get_IsGhostMode()) {
            return BaseScanner.scanNextBase();
        }

        // Base hasnt loaded yet.
        if (scanBase.GetBuildingsConditionInPercent() === 0) {
            base.failCount++;
            if (base.failCount === MAX_FAILS) {
                return BaseScanner.scanNextBase();
            }

            return setTimeout(function() {
                BaseScanner.getBaseLayout(base);
            }, 100);
        }

        var baseName = scanBase.get_Name();
        if (baseName !== 'Camp' && baseName !== 'Outpost' && baseName !== 'Base') {
            return BaseScanner.scanNextBase();
        }

        base.layout = BaseScanner.getLayout(scanBase);
        base.name = baseName;

        BaseScanner._bases[base.x + ':' + base.y] = base;

        // cache the base in local storage
        ST.storage.set('base-' + base.x + ':' + base.y, base);


        var data = {
            'base': base,
            'world': ClientLib.Data.MainData.GetInstance().get_Server().get_WorldId(),
            'player': ClientLib.Data.MainData.GetInstance().get_Player().get_Name()
        };

        BaseScanner._count ++;
        ST.util.api('scanBase', data, function() {
            BaseScanner.printScanResults(base);
            BaseScanner.done();
        });

        BaseScanner.scanNextBase();
    },

    scanNextBase: function() {
        if (BaseScanner.index === undefined) {
            BaseScanner.index = 0;
        } else {
            BaseScanner.index++;
        }

        var base = BaseScanner._toScan[BaseScanner.index];

        BaseScanner.getBaseLayout(base);
    },

    isScanning: function() {
        return BaseScanner._scanning === true;
    },

    printScanResults: function(base) {
        ST.util.button.setLabel(('   ' + BaseScanner.index).slice(-3) + '/' + BaseScanner._toScan.length);
        console.log('[' + ('   ' + BaseScanner.index).slice(-3) + '/' + BaseScanner._toScan.length + ']\t' + base.x + ':' + base.y + ' ' + base.layout + ' (' + base.failCount + ')');
    },

    getLayout: function(base) {
        var layout = [];
        for (var y = 0; y < 16; y++) {
            for (var x = 0; x < 9; x++) {
                var resourceType = base.GetResourceType(x, y);

                switch (resourceType) {
                    case 0:
                        // Nothing
                        layout.push('.');
                        break;
                    case 1:
                        // Crystal
                        layout.push('c');
                        break;
                    case 2:
                        // Tiberium
                        layout.push('t');
                        break;
                    case 4:
                        // Woods
                        layout.push('j');
                        break;
                    case 5:
                        // Scrub
                        layout.push('h');
                        break;
                    case 6:
                        // Oil
                        layout.push('l');
                        break;
                    case 7:
                        // Swamp
                        layout.push('k');
                        break;
                }
            }
        }
        return layout.join('');
    },

    setCurrentBase: function(baseID) {
        var allCities = ClientLib.Data.MainData.GetInstance().get_Cities().get_AllCities().d;
        var selectedBase = allCities[baseID];

        ClientLib.Vis.VisMain.GetInstance().CenterGridPosition(selectedBase.get_PosX(), selectedBase.get_PosY());
        ClientLib.Vis.VisMain.GetInstance().Update();
        ClientLib.Vis.VisMain.GetInstance().ViewUpdate();
        BaseScanner._lastBaseID = baseID;
    },

    getOfflineBase: function(x, y) {
        return ST.storage.get('base-' + x + ':' + y);

    },

    startup: function() {
        PatchClientLib.patch();
        // Listen for base changes
        phe.cnc.Util.attachNetEvent(ClientLib.Vis.VisMain.GetInstance(),
            'SelectionChange', ClientLib.Vis.SelectionChange,
            BaseScanner, BaseScanner.onSelectionChange);
    },

    destroy: function() {
        phe.cnc.Util.detachNetEvent(ClientLib.Vis.VisMain.GetInstance(),
            'SelectionChange', ClientLib.Vis.SelectionChange,
            BaseScanner, BaseScanner.onSelectionChange);
    },

    onSelectionChange: function() {
        BaseScanner.failCount = 0;
        try {
            // Maybe we caused the change in selection
            if (BaseScanner.isScanning()) {
                return;
            }

            if (BaseScanner.selectionChange !== undefined) {
                clearTimeout(BaseScanner.selectionChange);
                BaseScanner.selectionChange = undefined;
            }
            var currentObj = ClientLib.Vis.VisMain.GetInstance().get_SelectedObject();
            if (currentObj === null) {
                return;
            }

            var currentType = currentObj.get_VisObjectType();
            if (currentType === ClientLib.Vis.VisObject.EObjectType.RegionNPCCamp ||
                currentType === ClientLib.Vis.VisObject.EObjectType.RegionNPCBase) {
                BaseScanner.scanCurrentBase();
            }

        } catch (e) {
            ST.log.warn('Error in selection change', e);
        }
    },

    scanCurrentBase: function() {
        var data;
        var cities = ClientLib.Data.MainData.GetInstance().get_Cities();
        var base = cities.get_CurrentCity();
        BaseScanner.failCount++;
        if (BaseScanner.failCount > MAX_FAILS) {
            return;
        }

        if (base === null) {
            BaseScanner.selectionChange = setTimeout(BaseScanner.scanCurrentBase, 100);
            return;
        }


        var obj = {
            x: base.get_PosX(),
            y: base.get_PosY(),
            id: base.get_Id()
        };

        // already scanned
        var offlineBase = BaseScanner.getOfflineBase(obj.x, obj.y);
        if (offlineBase !== null && offlineBase.id === obj.id) {
            delete offlineBase.obj;
            BaseScanner._bases[obj.x + ':' + obj.y] = offlineBase;
            BaseScanner.failCount = 0;
            data = {
                'base': offlineBase,
                'world': ClientLib.Data.MainData.GetInstance().get_Server().get_WorldId(),
                'player': ClientLib.Data.MainData.GetInstance().get_Player().get_Name(),
                'alliance': ClientLib.Data.MainData.GetInstance().get_Alliance().get_Id()
            };

            BaseScanner._count ++;
            ST.util.api('scanBase', data, BaseScanner.done);
            return;
        }

        if (base.get_IsGhostMode()) {
            BaseScanner.failCount = 0;
            return;
        }

        if (base.GetBuildingsConditionInPercent() === 0) {
            BaseScanner.selectionChange = setTimeout(BaseScanner.scanCurrentBase, 100);
            return;
        }

        BaseScanner.failCount = 0;

        var baseName = base.get_Name();
        if (baseName !== 'Camp' && baseName !== 'Outpost' && baseName !== 'Base') {
            return;
        }

        obj.layout = BaseScanner.getLayout(base);
        obj.name = baseName;
        obj.alliance = ClientLib.Data.MainData.GetInstance().get_Alliance().get_Id();

        BaseScanner._bases[obj.x + ':' + obj.y] = obj;
        BaseScanner._selectionBases[obj.x + ':' + obj.y] = obj;

        // cache the base in local storage
        ST.storage.set('base-' + obj.x + ':' + obj.y, JSON.stringify(obj));
        ST.log.info('[BaseScanner:AutoScan] ' + obj.x + ':' + obj.y + ' ' + obj.layout);

        data = {
            'base': obj,
            'world': ClientLib.Data.MainData.GetInstance().get_Server().get_WorldId(),
            'player': ClientLib.Data.MainData.GetInstance().get_Player().get_Name(),
            'alliance': ClientLib.Data.MainData.GetInstance().get_Alliance().get_Id()
        };

        ST.util.api('scanBase', data, function() {
            console.log('SAVED BASE', data, arguments);
        });
    }
};


var PatchClientLib = {
    _g: function(k, r, q, m) {
        var p = [];
        var o = k.toString();
        var n = o.replace(/\s/gim, '');
        p = n.match(r);
        var l;
        for (l = 1; l < (m + 1); l++) {
            if (p !== null && p[l].length === 6) {
                console.debug(q, l, p[l]);
            } else {
                if (p !== null && p[l].length > 0) {
                    console.warn(q, l, p[l]);
                } else {
                    console.error('Error - ', q, l, 'not found');
                    console.warn(q, n);
                }
            }
        }
        return p;
    },

    patch: function() {
        if (BaseScanner._patched) {
            return;
        }

        var t = ClientLib.Data.WorldSector.WorldObjectCity.prototype;
        var re = /this\.(.{6})=\(?\(?g>>8\)?\&.*d\+=f;this\.(.{6})=\(/;
        var y = PatchClientLib._g(t.$ctor, re, ClientLib.Data.WorldSector.WorldObjectCity, 2);
        if (y !== null && y[1].length === 6) {
            t.getLevel = function() {
                return this[y[1]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectCity.Level undefined');
        }
        if (y !== null && y[2].length === 6) {
            t.getID = function() {
                return this[y[2]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectCity.ID undefined');
        }

        t = ClientLib.Data.WorldSector.WorldObjectNPCBase.prototype;
        re = /100\){0,1};this\.(.{6})=Math.floor.*d\+=f;this\.(.{6})=\(/;
        var x = PatchClientLib._g(t.$ctor, re, 'ClientLib.Data.WorldSector.WorldObjectNPCBase', 2);
        if (x !== null && x[1].length === 6) {
            t.getLevel = function() {
                return this[x[1]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectNPCBase.Level undefined');
        }
        if (x !== null && x[2].length === 6) {
            t.getID = function() {
                return this[x[2]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectNPCBase.ID undefined');
        }

        t = ClientLib.Data.WorldSector.WorldObjectNPCCamp.prototype;
        re = /100\){0,1};this\.(.{6})=Math.floor.*this\.(.{6})=\(*g\>\>(22|0x16)\)*\&.*=-1;\}this\.(.{6})=\(/;
        var w = PatchClientLib._g(t.$ctor, re, 'ClientLib.Data.WorldSector.WorldObjectNPCCamp', 4);
        if (w !== null && w[1].length === 6) {
            t.getLevel = function() {
                return this[w[1]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectNPCCamp.Level undefined');
        }
        if (w !== null && w[2].length === 6) {
            t.getCampType = function() {
                return this[w[2]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectNPCCamp.CampType undefined');
        }
        if (w !== null && w[4].length === 6) {
            t.getID = function() {
                return this[w[4]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectNPCCamp.ID undefined');
        }

        BaseScanner._patched = true;
    }
};



ST.register(BaseScanner);/* End: client/modules/basescanner.js */
/* Begin: client/modules/playerinfo.js */
/* globals ClientLib, GAMEDATA, ST */

var PlayerInfo = {
    name: 'PlayerInfo',

    instance: null,
    output: {},
    versions: {},

    getInfo: function() {
        PlayerInfo.patchClientLib();
        console.time('ST:getInfo');

        var oldVersions = {};
        Object.keys(PlayerInfo.versions).forEach(function(o){
            oldVersions[o] = PlayerInfo.versions[o];
        });

        // ST.log.debug('getInfo');
        PlayerInfo.instance = ClientLib.Data.MainData.GetInstance();
        PlayerInfo.output.world = PlayerInfo.instance.get_Server().get_WorldId();
        PlayerInfo.output.worldname = PlayerInfo.instance.get_Server().get_Name();

        PlayerInfo._getPlayerInfo();
        PlayerInfo._getNextMVC();
        PlayerInfo._getCities();

        ST.log.debug(PlayerInfo.output);
        console.timeEnd('ST:getInfo');

        var shouldUpdate = false;
        Object.keys(PlayerInfo.versions).forEach(function(o){
            if (PlayerInfo.versions[o] !== oldVersions[o]) {
                shouldUpdate = true;
            }
        });

        if (shouldUpdate) {
            PlayerInfo.saveInfo();
        }
    },

    _getPlayerInfo: function() {
        // ST.log.debug('\t getPlayerInfo');
        var player = PlayerInfo.instance.get_Player();
        PlayerInfo.output.id = player.get_Id();
        PlayerInfo.output.faction = PlayerInfo.map.faction[player.get_Faction()];
        PlayerInfo.output.player = player.get_Name();
        PlayerInfo.output.score = player.get_ScorePoints();
        PlayerInfo.output.rank = player.get_OverallRank();

        var sub = PlayerInfo.instance.get_PlayerSubstitution().getOutgoing();
        if (sub) {
            PlayerInfo.output.sub = sub.n;
        }

        var alliance = PlayerInfo.instance.get_Alliance();
        PlayerInfo.output.alliance = {
            id: alliance.get_Id(),
            name: alliance.get_Name(),
            bonus: {
                power: alliance.get_POIPowerBonus(),
                crystal: alliance.get_POICrystalBonus(),
                tiberium: alliance.get_POITiberiumBonus(),
                air: alliance.get_POIAirBonus(),
                def: alliance.get_POIDefenseBonus(),
                vec: alliance.get_POIVehicleBonus(),
                inf: alliance.get_POIInfantryBonus()
            },
            players: []
        };

        PlayerInfo.output.rp = player.get_ResearchPoints();
        PlayerInfo.output.credit = player.get_Credits().Base;

        PlayerInfo.output.command = {
            max: player.GetCommandPointMaxStorage(),
            current: player.GetCommandPointCount()
        };
    },

    _getNextMVC: function() {
        // ST.log.debug('\t getNextMVC');
        var player = PlayerInfo.instance.get_Player();

        var TechId = ClientLib.Base.Tech.GetTechIdFromTechNameAndFaction(
            ClientLib.Base.ETechName.Research_BaseFound, player.get_Faction());
        var PlayerResearch = player.get_PlayerResearch();
        var ResearchItem = PlayerResearch.GetResearchItemFomMdbId(TechId);

        if (ResearchItem === null) {
            return;
        }
        var NextLevelInfo = ResearchItem.get_NextLevelInfo_Obj();

        var resourcesNeeded = [];
        for (var i in NextLevelInfo.rr) {
            if (NextLevelInfo.rr[i].t > 0) {
                resourcesNeeded[NextLevelInfo.rr[i].t] = NextLevelInfo.rr[i].c;
            }
        }
        var creditsNeeded = resourcesNeeded[ClientLib.Base.EResourceType.Gold];
        var creditsResourceData = player.get_Credits();
        var creditsGrowthPerHour = (creditsResourceData.Delta + creditsResourceData.ExtraBonusDelta) *
            ClientLib.Data.MainData.GetInstance().get_Time().get_StepsPerHour();
        var creditsTimeLeftInHours = (creditsNeeded - player.GetCreditsCount()) / creditsGrowthPerHour;

        var mcvTime = creditsTimeLeftInHours * 60 * 60;
        if (mcvTime !== Infinity && !isNaN(mcvTime)) {
            PlayerInfo.output.mcvtime = mcvTime;
        }
    },


    _getCities: function() {
        // ST.log.debug('\t getCities');
        PlayerInfo.output.bases = [];
        var allCities = ClientLib.Data.MainData.GetInstance().get_Cities().get_AllCities().d;
        for (var selectedBaseID in allCities) {
            if (!allCities.hasOwnProperty(selectedBaseID)) {
                continue;
            }

            var selectedBase = allCities[selectedBaseID];
            if (selectedBase === undefined) {
                throw new Error('unable to find base: ' + selectedBaseID);
            }

            PlayerInfo._getCity(selectedBase);
        }
    },

    _getCity: function(c) {
        // ST.log.debug('\t\t getCity - ' + c.get_Name());
        var base = {};


        PlayerInfo.output.repair = c.GetResourceMaxStorage(ClientLib.Base.EResourceType.RepairChargeInf);

        base.defense = c.get_LvlDefense();
        base.offense = c.get_LvlOffense();

        base.power = c.GetResourceGrowPerHour(ClientLib.Base.EResourceType.Power, false, false) +
            c.GetResourceBonusGrowPerHour(ClientLib.Base.EResourceType.Power);

        base.tiberium = c.GetResourceGrowPerHour(ClientLib.Base.EResourceType.Tiberium, false, false) +
            c.GetResourceBonusGrowPerHour(ClientLib.Base.EResourceType.Tiberium);

        base.crystal = c.GetResourceGrowPerHour(ClientLib.Base.EResourceType.Crystal, false, false) +
            c.GetResourceBonusGrowPerHour(ClientLib.Base.EResourceType.Crystal);

        base.credits = ClientLib.Base.Resource.GetResourceGrowPerHour(c.get_CityCreditsProduction(), false) +
            ClientLib.Base.Resource.GetResourceBonusGrowPerHour(c.get_CityCreditsProduction(), false);

        base.health = c.GetBuildingsConditionInPercent();

        base.current = {};
        base.current.power = c.GetResourceCount(ClientLib.Base.EResourceType.Power);
        base.current.tiberium = c.GetResourceCount(ClientLib.Base.EResourceType.Tiberium);
        base.current.crystal = c.GetResourceCount(ClientLib.Base.EResourceType.Crystal);

        base.level = c.get_LvlBase();

        base.id = c.get_Id();

        base.x = c.get_PosX();
        base.y = c.get_PosY();
        base.v = c.get_Version();

        base.buildings = PlayerInfo._getBuildings(c, base);
        base.units = PlayerInfo._getUnits(c, base);

        base.repair = {};
        base.repair.infantry = c.get_CityUnitsData().GetRepairTimeFromEUnitGroup(ClientLib.Data.EUnitGroup.Infantry, false);
        base.repair.vehicle = c.get_CityUnitsData().GetRepairTimeFromEUnitGroup(ClientLib.Data.EUnitGroup.Vehicle, false);
        base.repair.air = c.get_CityUnitsData().GetRepairTimeFromEUnitGroup(ClientLib.Data.EUnitGroup.Aircraft, false);
        base.repair.time = c.GetResourceCount(ClientLib.Base.EResourceType.RepairChargeInf);

        base.name = c.get_Name();
        if (typeof base.name === 'string') {
            base.name.replace(/\./g, '');
        }
        PlayerInfo.output.bases.push(base);
        PlayerInfo.versions[base.name] = base.v;
    },

    saveInfo: function() {
        ST.util.api('savePlayer', PlayerInfo.output);
    },

    _getUnits: function(base) {
        var D = {};
        var O = {};
        var x, y, o;
        for (var k in base) {
            var currentFunc = base[k];
            if (typeof currentFunc !== 'object') {
                continue;
            }

            for (var k2 in currentFunc) {
                var listObj = currentFunc[k2];
                if (listObj === null || typeof listObj !== 'object' || listObj.d === undefined) {
                    continue;
                }

                var lst = listObj.d;
                if (typeof lst !== 'object') {
                    continue;
                }

                for (var i in lst) {
                    var unit = lst[i];
                    if (typeof unit !== 'object' || unit.get_UnitGameData_Obj === undefined) {
                        continue;
                    }
                    var name = unit.get_UnitGameData_Obj().n;
                    x = unit.get_CoordX();
                    y = unit.get_CoordY();
                    var level = unit.get_CurrentLevel();
                    var dName = PlayerInfo.map.defense[name];
                    var oName = PlayerInfo.map.offense[name];
                    if (dName !== undefined) {
                        D[x + ':' + y] = level + dName;
                    }
                    if (oName !== undefined) {
                        O[x + ':' + y] = level + oName;
                    }
                }
            }

        }
        var out = [];
        for (y = 0; y < 8; y++) {
            for (x = 0; x < 9; x++) {
                o = D[x + ':' + y];
                if (o === undefined) {
                    out.push(PlayerInfo.getResourceType(base.GetResourceType(x, y + 8)));
                } else {
                    out.push(o);
                }

            }
        }

        for (y = 0; y < 4; y++) {
            for (x = 0; x < 9; x++) {
                o = O[x + ':' + y];
                if (o === undefined) {
                    out.push('.');
                } else {
                    out.push(o);
                }

            }
        }

        return out.join('');
    },

    getResourceType: function(type) {
        switch (type) {
            case 0:
                // Nothing
                return '.';
            case 1:
                // Crystal
                return 'c';
            case 2:
                // Tiberium
                return 't';
            case 4:
                // Woods
                return 'j';
            case 5:
                // Scrub
                return 'h';
            case 6:
                // Oil
                return 'l';
            case 7:
                // Swamp
                return 'k';
        }
    },


    _getBuildings: function(base) {
        var buildings = base.get_Buildings();
        var buildingData = {};

        for (var b in buildings.d) {
            var build = buildings.d[b];
            buildingData[build.get_CoordX() + ':' + build.get_CoordY()] = build;
        }


        var layout = [];

        // buildings
        for (var y = 0; y < 8; y++) {
            for (var x = 0; x < 9; x++) {
                var resourceType = base.GetResourceType(x, y);
                var building = buildingData[x + ':' + y];
                var token = '.';
                var level = 1;

                if (building !== undefined) {
                    var info = GAMEDATA.Tech[building.get_MdbBuildingId()];
                    token = PlayerInfo.map.buildings[info.n];
                    level = building.get_CurrentLevel();
                }

                if (level > 1) {
                    layout.push(level);
                }

                switch (resourceType) {
                    case 0:
                        layout.push(token);
                        break;
                    case 1:
                        if (building === undefined) {
                            layout.push('c');
                        } else {
                            layout.push('n');
                        }
                        break;
                    case 2:
                        if (building === undefined) {
                            layout.push('t');
                        } else {
                            layout.push('h');
                        }

                        break;
                }
            }
        }

        return layout.join('');
    },

    startup: function() {
        PlayerInfo.getInfo();
        PlayerInfo.interval = setInterval(PlayerInfo.getInfo, 120000);
    },

    destroy: function() {
        if (PlayerInfo.interval === undefined) {
            return;
        }

        clearInterval(PlayerInfo.interval);
        PlayerInfo.interval = undefined;
    },
    patchClientLib: function() {
        var patches = [{
            proto: ClientLib.Data.WorldSector.WorldObjectCity.prototype,
            str: ClientLib.Data.WorldSector.WorldObjectCity.prototype.$ctor.toString(),
            funcs: {
                getBaseName: /this.(.{6})=c.substr/,
                getBaseHealth: /\}this.(.{6}).*if \(n/,
                getOwnerID: /(.{6})=\(\(g>>0x16/
            }
        }, {
            proto: ClientLib.Data.World.prototype,
            str: ClientLib.Data.World.prototype.GetSector.toString(),
            funcs: {
                getSectors: /\$r=this.(.{6})./
            }
        }, {
            proto: ClientLib.Data.WorldSector.prototype,
            str: ClientLib.Data.WorldSector.prototype.GetObject.toString(),
            funcs: {
                getBases: /\$r=this.(.{6})./
            }
        }];

        function makeReturn(str) {
            return function() {
                return this[str];
            };
        }

        for (var i = 0; i < patches.length; i++) {
            var patch = patches[i];
            var str = patch.str;

            var functionNames = Object.keys(patch.funcs);
            for (var j = 0; j < functionNames.length; j++) {
                var funcName = functionNames[j];
                var reg = patch.funcs[funcName];

                if (patch.proto[funcName] !== undefined) {
                    continue;
                }

                var matches = str.match(reg);
                if (!matches) {
                    console.error('Unable to map "' + funcName + '"');
                    continue;
                }

                patch.proto[funcName] = makeReturn(matches[1]);
            }

        }
    },

    map: {
        faction: {
            1: 'GDI',
            2: 'NOD'
        },

        buildings: {
            /* GDI Buildings */
            'GDI_Accumulator': 'a',
            'GDI_Refinery': 'r',
            'GDI_Trade Center': 'u',
            'GDI_Silo': 's',
            'GDI_Power Plant': 'p',
            'GDI_Construction Yard': 'y',
            'GDI_Airport': 'd',
            'GDI_Barracks': 'b',
            'GDI_Factory': 'f',
            'GDI_Defense HQ': 'q',
            'GDI_Defense Facility': 'w',
            'GDI_Command Center': 'e',
            'GDI_Support_Art': 'z',
            'GDI_Support_Air': 'x',
            'GDI_Support_Ion': 'i',

            /* Nod Buildings */
            'NOD_Refinery': 'r',
            'NOD_Power Plant': 'p',
            'NOD_Harvester': 'h',
            'NOD_Construction Yard': 'y',
            'NOD_Airport': 'd',
            'NOD_Trade Center': 'u',
            'NOD_Defense HQ': 'q',
            'NOD_Barracks': 'b',
            'NOD_Silo': 's',
            'NOD_Factory': 'f',
            'NOD_Harvester_Crystal': 'n',
            'NOD_Command Post': 'e',
            'NOD_Support_Art': 'z',
            'NOD_Support_Ion': 'i',
            'NOD_Accumulator': 'a',
            'NOD_Support_Air': 'x',
            'NOD_Defense Facility': 'w',
        },

        defense: {
            /* GDI Defense Units */
            'GDI_Wall': 'w',
            'GDI_Cannon': 'c',
            'GDI_Antitank Barrier': 't',
            'GDI_Barbwire': 'b',
            'GDI_Turret': 'm',
            'GDI_Flak': 'f',
            'GDI_Art Inf': 'r',
            'GDI_Art Air': 'e',
            'GDI_Art Tank': 'a',
            'GDI_Def_APC Guardian': 'g',
            'GDI_Def_Missile Squad': 'q',
            'GDI_Def_Pitbull': 'p',
            'GDI_Def_Predator': 'd',
            'GDI_Def_Sniper': 's',
            'GDI_Def_Zone Trooper': 'z',
            /* Nod Defense Units */
            'NOD_Def_Antitank Barrier': 't',
            'NOD_Def_Art Air': 'e',
            'NOD_Def_Art Inf': 'r',
            'NOD_Def_Art Tank': 'a',
            'NOD_Def_Attack Bike': 'p',
            'NOD_Def_Barbwire': 'b',
            'NOD_Def_Black Hand': 'z',
            'NOD_Def_Cannon': 'c',
            'NOD_Def_Confessor': 's',
            'NOD_Def_Flak': 'f',
            'NOD_Def_MG Nest': 'm',
            'NOD_Def_Militant Rocket Soldiers': 'q',
            'NOD_Def_Reckoner': 'g',
            'NOD_Def_Scorpion Tank': 'd',
            'NOD_Def_Wall': 'w',
        },

        offense: {
            /* GDI Offense Units */
            'GDI_APC Guardian': 'g',
            'GDI_Commando': 'c',
            'GDI_Firehawk': 'f',
            'GDI_Juggernaut': 'j',
            'GDI_Kodiak': 'k',
            'GDI_Mammoth': 'm',
            'GDI_Missile Squad': 'q',
            'GDI_Orca': 'o',
            'GDI_Paladin': 'a',
            'GDI_Pitbull': 'p',
            'GDI_Predator': 'd',
            'GDI_Riflemen': 'r',
            'GDI_Sniper Team': 's',
            'GDI_Zone Trooper': 'z',

            /* Nod Offense Units */
            'NOD_Attack Bike': 'b',
            'NOD_Avatar': 'a',
            'NOD_Black Hand': 'z',
            'NOD_Cobra': 'r',
            'NOD_Commando': 'c',
            'NOD_Confessor': 's',
            'NOD_Militant Rocket Soldiers': 'q',
            'NOD_Militants': 'm',
            'NOD_Reckoner': 'k',
            'NOD_Salamander': 'l',
            'NOD_Scorpion Tank': 'o',
            'NOD_Specter Artilery': 'p',
            'NOD_Venom': 'v',
            'NOD_Vertigo': 't',
            '': ''
        }

    }
};

ST.register(PlayerInfo);/* End: client/modules/playerinfo.js */
/* Begin: client/modules/basescount.js */
/* globals ClientLib, qx, webfrontend, ST */


var BaseCounter = {
    name: 'BaseCounter',
    ui: {},

    pasteOutput: function(x, y, baseCount, baseData) {
        var input = qx.core.Init.getApplication().getChat().getChatWidget().getEditable();
        var dom = input.getContentElement().getDomElement();

        var output = [];
        output.push(dom.value.substring(0, dom.selectionStart));
        output.push('[[coords]' + x + ':' + y + '[/coords]] Found ' + baseCount + ' Bases - ' + baseData);
        output.push(dom.value.substring(dom.selectionEnd, dom.value.length));

        input.setValue(output.join(' '));
    },

    countBases: function(x, y, paste) {
        var levelCount = [];
        var count = 0;
        var waves = 1;
        var maxAttack = 10;
        var world = ClientLib.Data.MainData.GetInstance().get_World();
        for (var scanY = y - 10; scanY <= y + 10; scanY++) {
            for (var scanX = x - 10; scanX <= x + 10; scanX++) {
                var distX = Math.abs(x - scanX);
                var distY = Math.abs(y - scanY);
                var distance = Math.sqrt((distX * distX) + (distY * distY));
                // too far away to scan
                if (distance > maxAttack) {
                    continue;
                }

                var object = world.GetObjectFromPosition(scanX, scanY);
                // Nothing to scan
                if (object === null) {
                    continue;
                }

                // Object isnt a NPC Base/Camp/Outpost
                if (object.Type !== ClientLib.Data.WorldSector.ObjectType.NPCBase) {
                    continue;
                }

                if (typeof object.getCampType === 'function' && object.getCampType() === ClientLib.Data.Reports.ENPCCampType.Destroyed) {
                    continue;
                }

                if (typeof object.getLevel !== 'function') {
                    BaseCounter._patchClientLib();
                }

                var level = object.getLevel();
                levelCount[level] = (levelCount[level] || 0) + 1;

                count++;
            }
        }

        if(count > 49) {
            waves = 5;
        } else if(count > 39) {
            waves = 4;
        } else if(count > 29) {
            waves = 3;
        } else if(count > 19) {
            waves = 2;
        }

        var output = [];
        for (var i = 0; i < levelCount.length; i++) {
            var lvl = levelCount[i];
            if (lvl !== undefined) {
                output.push(lvl + ' x ' + i);
            }
        }

        // console.log('[' + x + ':' + y + '] Found ' + count + ' bases - ' + output.join(', '));
        if (paste === undefined || paste === true) {
            BaseCounter.pasteOutput(x, y, count, output.join(', '), 'waves:', waves);
        }
        return {
            total: count,
            levels: levelCount,
            formatted: output.join(', '),
            waves: waves
        };
    },

    count: function(paste) {
        if (BaseCounter.selectedBase === null || BaseCounter.selectedBase === undefined) {
            return;
        }

        return BaseCounter.countBases(BaseCounter.selectedBase.get_RawX(), BaseCounter.selectedBase.get_RawY(), paste);
    },

    onRegionShow: function(c) {
        try {
            var target = c.getTarget();
            var object = target.getLayoutParent().getObject();

            var count = BaseCounter.countBases(object.get_RawX(), object.get_RawY(), false);

            BaseCounter.ui.region.total.setValue(count.total);
            BaseCounter.ui.region.levels.setValue(count.formatted);
            BaseCounter.ui.region.waves.setValue(' [ ' + count.waves + ' waves ]');

            target.add(BaseCounter.ui.region.container);
        } catch (e) {
            console.error(e);
        }
    },

    onBaseMoveChange: function(x, y) {
        var coord = ClientLib.Base.MathUtil.EncodeCoordId(x, y);
        var count = BaseCounter.moveCache[coord];

        if (count === undefined) {
            count = BaseCounter.countBases(x, y, false);
            BaseCounter.moveCache[coord] = count;
        }

        BaseCounter.ui.move.total.setValue(count.total);
        BaseCounter.ui.move.levels.setValue(count.formatted);
        BaseCounter.ui.move.waves.setValue(' [ ' + count.waves + ' waves ]');
    },

    onBaseMoveDeActivate: function() {
        BaseCounter.moveCache = {};
    },

    onBaseMoveActivate: function() {
        BaseCounter.moveCache = {};
    },

    buildMoveUI: function() {
        BaseCounter.ui.move = {};

        var a = new qx.ui.container.Composite(new qx.ui.layout.HBox(6));
        a.add(new qx.ui.basic.Label('# Forgotten bases:').set({
            alignY: 'middle'
        }));
        BaseCounter.ui.move.total = new qx.ui.basic.Label().set({
            alignY: 'middle',
            font: 'bold',
            textColor: 'text-region-value'
        });
        a.add(BaseCounter.ui.move.total);

        BaseCounter.ui.move.waves = new qx.ui.basic.Label().set({
            textColor: 'text-region-value'
        });
        a.add(BaseCounter.ui.move.waves);

        var b = new qx.ui.container.Composite(new qx.ui.layout.HBox(6));
        b.add(new qx.ui.basic.Label('Levels:').set({
            alignY: 'middle'
        }));
        BaseCounter.ui.move.levels = new qx.ui.basic.Label().set({
            alignY: 'middle',
            font: 'bold',
            textColor: 'text-region-value'
        });
        b.add(BaseCounter.ui.move.levels);


        BaseCounter.ui.move.container = new qx.ui.container.Composite(new qx.ui.layout.VBox()).set({
            textColor: 'text-region-tooltip'
        });

        BaseCounter.ui.move.container.add(a);
        BaseCounter.ui.move.container.add(b);
        webfrontend.gui.region.RegionCityMoveInfo.getInstance().addAt(BaseCounter.ui.move.container, 3);
    },

    buildRegionUI: function() {
        BaseCounter.ui.region = {};

        var a = new qx.ui.container.Composite(new qx.ui.layout.HBox(4));
        a.add(new qx.ui.basic.Label('# Forgotten bases:'));
        BaseCounter.ui.region.total = new qx.ui.basic.Label().set({
            textColor: 'text-region-value'
        });
        a.add(BaseCounter.ui.region.total);

        BaseCounter.ui.region.waves = new qx.ui.basic.Label().set({
            textColor: 'text-region-value'
        });
        a.add(BaseCounter.ui.region.waves);

        var b = new qx.ui.container.Composite(new qx.ui.layout.HBox(4));
        b.add(new qx.ui.basic.Label('Levels:'));
        BaseCounter.ui.region.levels = new qx.ui.basic.Label().set({
            textColor: 'text-region-value'
        });
        b.add(BaseCounter.ui.region.levels);

        BaseCounter.ui.region.container = new qx.ui.container.Composite(new qx.ui.layout.VBox()).set({
            marginTop: 6,
            textColor: 'text-region-tooltip'
        });

        BaseCounter.ui.region.container.add(a);
        BaseCounter.ui.region.container.add(b);
    },

    startup: function() {
        if (typeof webfrontend.gui.region.RegionCityInfo.prototype.getObject !== 'function') {
            var a = webfrontend.gui.region.RegionCityInfo.prototype.setObject.toString();
            var b = a.match(/^function[^;]+;\W*var\W*([A-Za-z]+)[^\}]+\}\s;.*.+this\.([A-Za-z_]+)=\1;/);
            
            if(b) {
                webfrontend.gui.region.RegionCityInfo.prototype.getObject = function() {
                    return this[b[2]];
                };
            }
        }

        BaseCounter.bindings = [
            webfrontend.gui.region.RegionCityStatusInfoOwn,
            webfrontend.gui.region.RegionCityStatusInfoAlliance,
            webfrontend.gui.region.RegionCityStatusInfoEnemy,
            webfrontend.gui.region.RegionNPCBaseStatusInfo,
            webfrontend.gui.region.RegionNPCCampStatusInfo,
            webfrontend.gui.region.RegionRuinStatusInfo
        ];

        BaseCounter._listeners = [];
        for (var i = 0; i < BaseCounter.bindings.length; i++) {
            var bind = BaseCounter.bindings[i];
            var bindID = bind.getInstance().addListener('appear', BaseCounter.onRegionShow);
            BaseCounter._listeners[i] = bindID;
        }

        var mouseTool = ClientLib.Vis.VisMain.GetInstance().GetMouseTool(ClientLib.Vis.MouseTool.EMouseTool.MoveBase);
        phe.cnc.Util.attachNetEvent(mouseTool, 'OnCellChange', ClientLib.Vis.MouseTool.OnCellChange, BaseCounter, BaseCounter.onBaseMoveChange);
        phe.cnc.Util.attachNetEvent(mouseTool, 'OnDeactivate', ClientLib.Vis.MouseTool.OnDeactivate, BaseCounter, BaseCounter.onBaseMoveDeActivate);
        phe.cnc.Util.attachNetEvent(mouseTool, 'OnActivate', ClientLib.Vis.MouseTool.OnActivate, BaseCounter, BaseCounter.onBaseMoveActivate);

        BaseCounter.buildRegionUI();
        BaseCounter.buildMoveUI();
        BaseCounter.registerButton();
    },

    destroy: function() {
        for (var i = 0; i < BaseCounter.bindings.length; i++) {
            var bindID = BaseCounter._listeners[i];
            if (bindID !== undefined) {
                BaseCounter.bindings[i].getInstance().removeListenerById(bindID);
            }
        }
        BaseCounter._listeners = [];

        var mouseTool = ClientLib.Vis.VisMain.GetInstance().GetMouseTool(ClientLib.Vis.MouseTool.EMouseTool.MoveBase);
        phe.cnc.Util.detachNetEvent(mouseTool, 'OnCellChange', ClientLib.Vis.MouseTool.OnCellChange, BaseCounter, BaseCounter.onBaseMoveChange);
        phe.cnc.Util.detachNetEvent(mouseTool, 'OnDeactivate', ClientLib.Vis.MouseTool.OnDeactivate, BaseCounter, BaseCounter.onBaseMoveDeActivate);
        phe.cnc.Util.detachNetEvent(mouseTool, 'OnActivate', ClientLib.Vis.MouseTool.OnActivate, BaseCounter, BaseCounter.onBaseMoveActivate);

        webfrontend.gui.region.RegionCityMoveInfo.getInstance().removeAt(3);
    },

    onShow: function(c) {
        console.log(c);
    },

    registerButton: function() {
        if (!webfrontend.gui.region.RegionCityMenu.prototype.__baseCounterButton_showMenu) {
            webfrontend.gui.region.RegionCityMenu.prototype.__baseCounterButton_showMenu = webfrontend.gui.region.RegionCityMenu.prototype.showMenu;

            webfrontend.gui.region.RegionCityMenu.prototype.showMenu = function(selectedVisObject) {
                BaseCounter.selectedBase = selectedVisObject;
                if (this.__baseCounterButton_initialized !== true) {
                    this.__baseCounterButton_initialized = true;

                    this.__baseCountButton = new qx.ui.form.Button('Paste BaseCount');
                    this.__baseCountButton.addListener('execute', function() {
                        BaseCounter.count();
                    });
                }


                if (BaseCounter.lastBase !== BaseCounter.selectedBase) {
                    var count = BaseCounter.count(false);
                    this.__baseCountButton.setLabel('Bases: ' + count.total  + ' [' + count.waves + ']');
                    BaseCounter.lastBase = BaseCounter.selectedBase;
                }
                // console.log(children);
                this.__baseCounterButton_showMenu(selectedVisObject);
                switch (selectedVisObject.get_VisObjectType()) {
                    case ClientLib.Vis.VisObject.EObjectType.RegionNPCCamp:
                    case ClientLib.Vis.VisObject.EObjectType.RegionNPCBase:
                    case ClientLib.Vis.VisObject.EObjectType.RegionPointOfInterest:
                    case ClientLib.Vis.VisObject.EObjectType.RegionRuin:
                    case ClientLib.Vis.VisObject.EObjectType.RegionHubControl:
                    case ClientLib.Vis.VisObject.EObjectType.RegionHubServer:
                    case ClientLib.Vis.VisObject.EObjectType.RegionCityType:
                        this.add(this.__baseCountButton);
                        break;
                    default:
                        console.log(selectedVisObject.get_VisObjectType());
                }
            };
        }

    },

    _patchClientLib: function() {
        var proto = ClientLib.Data.WorldSector.WorldObjectNPCBase.prototype;
        var re = /100\){0,1};this\.(.{6})=Math.floor.*d\+=f;this\.(.{6})=\(/;
        var x = ST.util._g(proto.$ctor, re, 'ClientLib.Data.WorldSector.WorldObjectNPCBase', 2);
        if (x !== null && x[1].length === 6) {
            proto.getLevel = function() {
                return this[x[1]];
            };
        } else {
            console.error('Error - ClientLib.Data.WorldSector.WorldObjectNPCBase.Level undefined');
        }

    }

};

ST.register(BaseCounter);/* End: client/modules/basescount.js */
/* Begin: client/modules/supportstats.js */
/* globals ClientLib, GAMEDATA, ST */
var SupportStats = {
    name: 'SupportStats',

    _players: {},
    _supports: {},
    _levels: {},
    _alliance: {},
    _stats: [],

    refresh: function() {
        SupportStats.reset();
        SupportStats.getStats();
    },

    getStats: function() {
        SupportStats.reset();
        var allSupports = ClientLib.Data.MainData.GetInstance().get_AllianceSupportState().get_Bases().d;
        var allPlayers = ClientLib.Data.MainData.GetInstance().get_Alliance().get_MemberData().d;

        var AllianceName = ClientLib.Data.MainData.GetInstance().get_Alliance().get_Name();

        var keys = Object.keys(allSupports);
        SupportStats.addStat(AllianceName, null, SupportStats._alliance);

        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            var support = allSupports[key];

            var player = allPlayers[support.get_PlayerId()];
            if (player === undefined) {
                continue;
            }

            var stats = {
                x: support.get_X(),
                y: support.get_Y(),
                level: support.get_Level(),
                player: player.Name,
                type: support.get_Type(),
                name: GAMEDATA.supportTechs[support.get_Type()].dn
            };

            SupportStats._stats.push(stats);

            SupportStats.addStat(player.Name, stats, SupportStats._players, player);
            SupportStats.addStat(stats.type, stats, SupportStats._supports);
            SupportStats.addStat(stats.level, stats, SupportStats._levels);
            SupportStats.addStat(AllianceName, stats, SupportStats._alliance);
        }

        for (var playerId in allPlayers) {
            var alliancePlayer = allPlayers[playerId];
            SupportStats.addStat(alliancePlayer.Name, null, SupportStats._players, alliancePlayer);
            SupportStats._alliance[AllianceName].bases += alliancePlayer.Bases;
        }
    },

    getPlayers: function() {
        SupportStats.getStats();

        var data = [];
        var players = Object.keys(SupportStats._players);
        for (var i = 0; i < players.length; i++) {
            var player = players[i];
            var stats = SupportStats._players[player];

            data.push(stats);
        }

        data.sort(function(a, b) {
            return b.average - a.average;
        });

        var finalOutput = [];
        data.forEach(function(o) {
            var output = [
                (o.average || 0).toFixed(2),
                o.name
            ];

            finalOutput.push(output.join(' \t '));
        });

        return finalOutput;
    },


    print: function() {
        SupportStats.getStats();
        var AllianceName = ClientLib.Data.MainData.GetInstance().get_Alliance().get_Name();
        var output = [];
        var stats = SupportStats._alliance[AllianceName];
        output.push('Alliance Report for "' + AllianceName + '"');
        output.push('-----------');
        output.push('Bases:    ' + stats.bases + '\tSupports:    ' + stats.count + ' (' + ((stats.count / stats.bases) * 100).toFixed(2) + '%)');
        output.push('Average:  ' + (stats.level / stats.count).toFixed(2));
        output.push('-----------');
        output.push('Biggest Support');
        output.push('Player:   ' + stats.big_support.player);
        output.push('Type:     ' + stats.big_support.name);
        output.push('Level:    ' + stats.big_support.level + ' @ [coords]' + stats.big_support.x + ':' + stats.big_support.y + '[/coords]');
        output.push('-----------');
        output.push('Smallest Support');
        output.push('Player:   ' + stats.small_support.player);
        output.push('Type:     ' + stats.small_support.name);
        output.push('Level:    ' + stats.small_support.level + ' @ [coords]' + stats.small_support.x + ':' + stats.small_support.y + '[/coords]');
        output.push('-----------');
        output.push('Breakdown');
        output.push('-----------');

        for (var supportId in GAMEDATA.supportTechs) {
            var supportName = GAMEDATA.supportTechs[supportId];
            var supportStat = SupportStats._supports[supportId];

            var count = supportStat === undefined ? 0 : supportStat.count;
            var avg = supportStat === undefined ? 0 : (supportStat.level / supportStat.count);

            output.push(SupportStats.pad(supportName.dn, 18) + ' count:' + count + '   avg:' + avg);
        }

        output.push('-----------');
        output.push('Players');
        output.push('-----------');
        output.push('Average  Player');
        output = output.concat(SupportStats.getPlayers());

        console.log(output.join('\n'));
    },

    pad: function(str, len) {
        return str + new Array(len + 1 - str.length).join(' ');
    },

    addStat: function(name, support, obj, player) {
        var data = obj[name];

        if (data === undefined) {
            data = {
                name: name,
                count: 0,
                level: 0,
                big: 0,
                small: -1,
                support: []
            };
            if (player !== undefined) {
                data.bases = player.Bases;
            } else {
                data.bases = 0;
            }
            obj[name] = data;
        }

        if (support === null) {
            return;
        }

        data.count++;
        data.level += support.level;

        if (data.bases > 0) {
            data.average = data.level / data.bases;
        }

        if (support.level > data.big) {
            data.big = support.level;
            data.big_support = support;
        }

        if (support.level < data.small || data.small === -1) {
            data.small = support.level;
            data.small_support = support;
        }

        data.support.push(support);
    },

    reset: function() {
        SupportStats._players = {};
        SupportStats._supports = {};
        SupportStats._levels = {};
        SupportStats._stats = [];
        SupportStats._alliance = {};
    }
};

ST.register(SupportStats);/* End: client/modules/supportstats.js */
/* Begin: client/modules/killinfo.js */
var KillInfo = {
    name: 'KillInfo',
    /* globals $I */
    startup: function() {

        KillInfo.findPrototype();

        if (KillInfo.protoName === undefined) {
            ST.log.warn('ST:KillInfo - Cant find prototype');
            return;
        }
        var proto = $I[KillInfo.protoName];
        if (proto === undefined ||
            proto.prototype[KillInfo.funcName] === undefined) {
            ST.log.warn('ST:KillInfo - Cant find function');
            return;
        }

        KillInfo.oldFunc = proto.prototype[KillInfo.funcName];
        proto.prototype[KillInfo.funcName] = function(c) {
            if (typeof c.get_UnitDetails !== 'function') {
                return KillInfo.oldFunc.call(this, c);
            }

            KillInfo.oldFunc.call(this, c);
            if (ClientLib.Vis.VisMain.GetInstance().get_MouseMode() !== 0) {
                return;
            }

            var unit = c.get_UnitDetails();
            // TODO adjust plunder to hp
            // Does modifying the plunder object have any other effects
            // var hp = unit.get_HitpointsPercent();
            var plunder = unit.get_UnitLevelRepairRequirements();
            var data = unit.get_UnitGameData_Obj();

            if (this[KillInfo.internalObj] !== null) {
                this[KillInfo.internalObj][KillInfo.showFunc](data.dn, data.ds, plunder, '');
            }
        };
    },

    findPrototype: function() {
        var funcNameMatch = '"tnf:full hp needed to upgrade")';
        var funcContentMatch = 'DefenseTerrainFieldType';
        var funcName = '';

        function searchFunction(proto) {
            for (var j in proto) {
                if (j.length !== 6) {
                    continue;
                }
                var func = proto[j];
                if (typeof func === 'function') {
                    var str = func.toString();
                    if (str.indexOf(funcNameMatch) !== -1) {
                        console.log(j);
                        return j;
                    }
                }
            }
            return '';
        }

        for (var i in $I) {
            var obj = $I[i];
            if (obj.prototype === undefined) {
                continue;
            }
            if (funcName === '') {
                funcName = searchFunction(obj.prototype);
                if (funcName === '') {
                    continue;
                }
            }
            var func = obj.prototype[funcName];
            if (func === undefined) {
                continue;
            }
            var str = func.toString();

            // not the particular version we are looking for
            if (str.indexOf(funcContentMatch) === -1) {
                continue;
            }

            KillInfo.protoName = i;
            KillInfo.funcName = funcName;

            var matches = str.match(/(.{6}).(.{6})\(d,e,i,f\)/);
            if (matches !== null && matches.length === 3) {
                KillInfo.internalObj = matches[1];
                KillInfo.showFunc = matches[2];
            }

        }

    },

    destroy: function() {
        if (KillInfo.oldFunc === undefined) {
            return;
        }
        // reset the function
        $I[KillInfo.protoName].prototype[KillInfo.funcName] = KillInfo.oldFunc;
        KillInfo.oldFunc = undefined;
    }

};

ST.register(KillInfo);/* End: client/modules/killinfo.js */
    console.timeEnd('ST:LoadModules');
};
ST_MODULES.push(setupShockrModules);

function innerHTML(functions) {
    var output = [];
    for (var i = 0; i < functions.length; i++) {
        var func = functions[i];
        output.push('try {(  ' + func.toString() + ')()' +
            '} catch(e) { console.log("Error Registering function", e);};');
    }
    return output.join('\n\n');
}

if (window.location.pathname !== ('/login/auth')) {
    var script = document.createElement('script');
    script.innerHTML = innerHTML(ST_MODULES);
    script.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(script);
}