eliran123456 / CATool

// ==UserScript==
// @name        CATool
// @namespace   CATool
// @description Given indication about the chances of the user to take the base \ camp in precents
// @version     0.1
// @author      Eliran Givoni
// @include     http*://prodgame*.alliances.commandandconquer.com/*/index.aspx*
// ==/UserScript==

(function() {
    var chancesOfAttackTool = function() {
        var initCATool = function()
        {            
            var ThrowError = function(e) {
                console.log("CATool Error: ", e);
            };
            
            var get =
            {
                client : function() {
                    try {
                        return ClientLib.Data.MainData.GetInstance();
                    } catch (e) {
                        throw Error("Client lib error", e);
                    }
                    
                    return null;
                },
                cities : function() {
                    try {
                        return this.client().get_Cities();
                    } catch (e) {
                        throw Error("Cities of client error", e);
                    }
                    
                    return [];
                },
                allCities : function() {
                    try {
                        return this.cities().get_AllCities().d;
                    } catch (e) {
                        throw Error("Cities interfaces error", e);
                    }
                    
                    return [];
                },
                world : function() {
                    try {
                        return this.client().get_World();
                    } catch (e) {
                        throw Error("World object error", e);
                    }
                    
                    return [];
                },
                alliance : function() {
                    try {
                        return this.client().get_Alliance();
                    } catch (e) {
                        throw Error("Alliance object error", e);
                    }
                    
                    return [];
                },
                attackRange : function() {
                    try {
                        return this.client().get_Server().get_MaxAttackDistance() - 0.5;
                    } catch (e) {
                        throw Error("Can't create attack range", e);
                    }
                    
                    return 0;
                },
                
                citryEvents : ["Base", "Camp"],
                
                define :
                {
                    camp        : ClientLib.Data.WorldSector.ObjectType.NPCCamp,
                    base        : ClientLib.Data.WorldSector.ObjectType.NPCBase,
                    isDestroyed : ClientLib.Data.Reports.ENPCCampType.Destroyed,
                    maxFailed   : 20,
                    
                    maxUnitsDamage     : 3 * ( 6 * 7 ),
                    maxBuildingDestroy : 23,
                    
                    layoutPrecentText : "Percentage Recommendation attack: %d%",
                    layoutFailedText  : "Can't process percentage recommendation attack of the current base"
                },
                
                buildings : [ 58, 72, 73, 74 , 75,
                              77, 78, 79, 116, 117 ],
                
                units_type : [ "AV", "AI", "AA", "NN" ],
                
                defence_unit :
                {
                    178 : [ 0, 0, 2 ],
                    181 : [ 0, 0, [ 5, 3 ] ],
                    184 : [ 0, 0, 1 ],
                    187 : [ 0, 0, 2 ],
                    191 : [ 0, 0, 0 ],
                    208 : [ 0, 0, 2 ],
                    179 : [ 1, 0, 2 ],
                    182 : [ 1, 0, [ 5, 3 ] ],
                    185 : [ 1, 0, 1 ],
                    188 : [ 1, 0, 1 ],
                    190 : [ 1, 0, 0 ],
                    210 : [ 1, 0, 2 ],
                    186 : [ 2, 0, 1 ],
                    180 : [ 2, 0, 2 ],
                    183 : [ 2, 0, [ 5, 3 ] ], 
                    189 : [ 2, 0, 2 ],
                    192 : [ 3, 0, 0 ]
                }
            };
            
            var regex =
            {
                allowBaseType   : /^(camp|outpost|base|infected)$/i,
                followBuildings : /^(74|58)$/
            };
            
            var require =
            {
                runOverKey   : 0,
                baseFail     : {},
                basesObj     : {},
                follow       : [],
                stopScanning : false,
                
                /**
                 * @method {integer} getAllianceArmyBonuses(): get the average bonuses of the army
                 */
                
                getAllianceArmyBonuses : function() {
                    try {
                        var alliance = get.alliance();
                        return Math.round( ( alliance.get_POIAirBonus() + alliance.get_POIInfantryBonus() + alliance.get_POIVehicleBonus() ) / 3 );
                    } catch (e) {
                        ThrowError(e);
                    }
                    
                    return 0;
                },
                
                /**
                 * @method {void} getCities(): get all the owner cities
                 * @param {function} callback: set callback per each owner base
                 */
                
                getCities : function( callback )
                {
                    var allCities = {};
                    
                    try {
                        allCities = get.allCities();
                    } catch(e) {
                        ThrowError(e);
                    }
                    
                    for( var city in allCities ) {
                        if ( allCities.hasOwnProperty( city ) ) {
                            var ownBase = allCities[ city ];
                            
                            if ( ownBase === undefined ) {
                                throw new Error( "Unable to find own city " + city );
                            }
                            
                            callback( ownBase );
                        }
                    }
                },
                
                /**
                 * @method {object} getOwnCity(): get current own city
                 */
                
                getOwnCity : function() {
                    try {
                       return get.cities().get_CurrentOwnCity();
                    } catch (e) {
                        throw Error("Can't get current own city", e);
                    }
                    
                    return [];
                },
                
                /**
                 * @method {float} getOwnCityOffenseLevel(): get current own city offense level
                 */
                
                getOwnCityOffenseLevel : function() {
                    return parseFloat( this.getOwnCity().get_LvlOffense().toFixed(2) );  
                },
                
                /**
                 * @method {array} getBasesInRange(): get bases in the range of the owner bases
                 * @param {function} callback: set callback per each coord base
                 */
                
                getBasesInRange : function( callback ) {
                    var cacheBases = [];
                    var bases = [];
                    
                    this.getCities( function( baseObj ){
                        try {
                            var x = baseObj.get_PosX();
                            var y = baseObj.get_PosY();
                            
                            var base_level = baseObj.get_LvlBase();
                            var base_OL    = baseObj.get_LvlOffense();
                            
                            // Skip bases without offense
                            if ( base_OL === 0 ) {
                                return false;
                            }
                            
                            for ( var scanY = y - 11; scanY <= y + 11; scanY++ ) {
                                for ( var scanX = x - 11; scanX <= x + 11; scanX++ ) {
                                    var obj, coord = [ scanX, scanY ].join(":");
                                    
                                    var rangeX = Math.abs( x - scanX );
                                    var rangeY = Math.abs( y - scanY );
                                    var rangeT = Math.sqrt( ( rangeX * rangeX ) + ( rangeY * rangeY ) );
                                    
                                    // Continue if the attack range is invalid
                                    if ( rangeT > get.attackRange() || coord in cacheBases ) {
                                        continue;
                                    }
                                    
                                    // Get coord object
                                    if ( ! ( obj = require.getObjectFromCoord( scanX, scanY ) ) ) {
                                        continue;
                                    }
                                    
                                    obj.ownLevel = base_level;
                                    obj.ownOffenseLevel = base_OL;
                                    
                                    cacheBases.push( coord );
                                    bases.push( obj );
                                }
                            }
                            
                            return true;
                        } catch (e) {
                            ThrowError(e);
                        }
                        
                        return true;
                    });
                    
                    return bases;
                },
                
                /**
                 * @method {boolean} getObjectFromCoord(): get object in world from coord
                 * @param {int} scanX: coord x
                 * @param {int} scanY: coord y
                 */
                
                getObjectFromCoord : function( scanX, scanY ) {
                    try {
                        var obj = get.world().GetObjectFromPosition( scanX, scanY );
                                   
                        if ( obj === null ) {
                            return false;
                        }
                        
                        var objType = obj.Type;
                        var objCampType = obj.getCampType;
                        
                        if ( ( get.define.base !== objType && get.define.camp !== objType ) || ( typeof objCampType === "function" && objCampType() === get.define.isDestroyed ) ) {
                            return false;
                        }
                        
                        return obj;
                    } catch (e) {
                        ThrowError(e);
                    }
                    
                    return false;
                },
                
                /**
                 * @method {void} runOnEachBaseCoord(): run on each base coord
                 */
                
                runOnEachBaseCoord : function()
                {
                    this.runOverKey   = 0;
                    this.stopScanning = false;
                    this.basesObj     = this.getBasesInRange();
            
                    this.runOnNextBase();
                },
                
                /**
                 * @method {void} runOnBaseByCoord(): run on base in specific coord
                 * @param {int} x: coord x
                 * @param {int} y: coord y
                 */
                
                runOnBaseByCoord : function( x, y, c ) {
                    var obj = this.getObjectFromCoord( x, y );
                    
                    if ( obj ) {
                        this.stopScanning = false;
                        this.runOnNextBase( false, obj, c );
                    }
                },
                
                /**
                 * @method {void} runOnNextBase(): run on next base in array
                 * @param {boolean} nextKey: move to next key automaticlly
                 * @param {function} callback: success callback when the information recieved
                 */
                
                runOnNextBase : function( nextKey, base, callback )
                {
                    var scan = null;
                    
                    if ( nextKey ) {
                        this.runOverKey++;           
                    }
                    
                    if ( ! this.stopScanning && ( base = base || ( typeof this.basesObj[ this.runOverKey ] !== "undefined" ? this.basesObj[ this.runOverKey ] : null ) ) )
                    {
                        var baseId = base.getID();
            
                        try {
                            get.cities().set_CurrentCityId( baseId );
                            
                            scan = get.cities().GetCity( baseId );
                            scan.ownOffenseLevel = base.ownOffenseLevel;
                        } catch (e) {
                            ThrowError(e);
                        }
                        
                        if ( scan === null || scan.GetBuildingsConditionInPercent() === 0 )
                        {
                            // Write scan failed
                            if ( ! require.baseFail.hasOwnProperty( baseId ) ) {
                                require.baseFail[ baseId ] = 0;
                            }
                            
                            require.baseFail[ baseId ]++;
                            
                            // if the failed attemps pass the maximum
                            if ( require.baseFail[ baseId ] == get.define.maxFailed ) {
                                return require.runOnNextBase( true, base, callback );
                            }
            
                            // Try again if scan failed
                            return setTimeout( function(){
                                require.runOnNextBase( nextKey, base, callback );
                            }, 100 );
                        }
                        else {
                            if ( ! regex.allowBaseType.exec( scan.get_Name() ) ) {
                                return this.runOnNextBase( true, null, callback );
                            }
            
                            return this.cleanNeccVars() && this.scanBaseLayout( scan, callback );
                        }
                    }
                    
                    return true;
                },
                
                /**
                 * @method {void} scanBaseLayout(): get each base layout
                 * @param {ClientLib.GetCity} scan: get city scan result
                 * @param {function} callback: success callback when the information recieved
                 */
                
                scanBaseLayout : function( scan, callback )
                {
                    try {
                        var level      = scan.get_BaseLevel();
                        var buildings  = scan.get_Buildings().d;
                        
                        var total_avg  = {
                            buildings : 0,
                            defences  : 0
                        };
                
                        // if the buildings object is empty, return false
                        if ( Object.keys( buildings ).length > 0 )
                        {
                            total_avg.buildings = this.getBaseBuildingsComplexityAverage( buildings );
                            total_avg.defences  = this.getBaseDefenceComplexityAvarege( scan );
                            
                            total_avg.result = ( total_avg.buildings + total_avg.defences ) / 3;
                            total_avg.result = total_avg.result < 0 ? 0 : total_avg.result > 100 ? 100 : total_avg.result;
                            total_avg.result = Math.round( total_avg.result );
                            
                            callback && callback( total_avg.result, scan );
                        }
                    } catch (e) {
                        callback && callback( -1, scan );
                        ThrowError(e);
                    }
                    
                    this.runOnNextBase( true, null, callback );
                },
                
                /**
                 * @method {boolean} cleanNeccVars(): clean neccessery vars
                 */
                
                cleanNeccVars : function() {
                    this.follow = [];
                    return true;
                },
                
                /**
                 * @method {int} getBaseDefenceComplexityAvarege(): get the complexity of the base defence in average
                 * @param {ClientLib.Data} base: the scanned base
                 */
                
                getBaseDefenceComplexityAvarege : function( base )
                {
                    try {
                        var defences     = base.get_DefenseUnits().d;
                        var average      = 0;
                        var defenceLevel = 0;
                        
                        for( var i in defences ) {
                            var unit = defences[i];
                            
                            // Update defence level sum
                            defenceLevel += unit.get_CurrentLevel();
                            
                            // set unit details
                            var d = {
                                t : unit.get_MdbUnitId(),
                                x : unit.get_CoordX(),
                                y : unit.get_CoordY()
                            };
                            
                            // get unit info
                            var i = get.defence_unit[d.t];
                            
                            // don't support upgrade units
                            if ( i == undefined ) {
                                continue;
                            }
                            
                            // Scan unit possible area
                            var o = "xy";
                            var s = { x : [], y :[] }
                            
                            if ( typeof i[2] === "number" ) {
                                i[2] = [ i[2], 0 ];
                            }
                            
                            s.y[0] = d.y - i[2][0];
                            s.y[1] = d.y + i[2][0];
                            s.x[0] = d.x - i[2][0];
                            s.x[1] = d.x + i[2][0];
                
                            for( var c = 0; c <= 1; c++ ) {
                                var e = o[c];
                                var z = s[e];
                                
                                z = z.map( function(v, k){
                                    return v < 0 ? 0 : v > 8 ? 8 : v;
                                });
                
                                for( var f in require.follow ) {
                                    var v = require.follow[f];
                                    var r = v[e];
                                    
                                    if ( r >= z[0] && r <= z[1] ) {
                                        var a = Math.abs( z[1] - z[0] ) + 1;
                                        
                                        if ( i[2][1] > 0 ) {
                                            a -= ( i[2][1] * 2 ) - 1;
                                        }
                                        
                                        v.pu += a;
                                    }
                                }
                            }
                        }
                        
                        // Get the average of the defence level
                        defenceLevel /= Object.keys( defences ).length;
                        
                        // Set the offense level with alliance bonuses
                        var offenseLevelIncludeBonuses = this.getOwnCityOffenseLevel() + ( this.getAllianceArmyBonuses() / 100 );
                        
                        // Set the level range between own offense and defence in precent
                        var levelRangePrecent = Math.abs( parseFloat( ( ( offenseLevelIncludeBonuses / defenceLevel ) * 100 ).toFixed(2) ) );
                        
                        // Set the level range between own offense and defence
                        var levelRange = parseFloat( ( defenceLevel - offenseLevelIncludeBonuses ).toFixed(2) );
                
                        // Get the avarage of the under bases
                        for( var p in this.follow ) {
                            var pu = this.follow[p].pu;
                
                            average += pu;
                        }
                
                        // Calc the average of the base defence
                        average = ( average / this.follow.length ) + levelRange;
                        average = average <= 0 ? 1 : average;
                
                        // Return the average of the unit defence and offense
                        return Math.abs( Math.round( ( average / get.define.maxUnitsDamage ) * 100 ) - 100 ) + levelRangePrecent;
                    } catch (e) {
                        throw Error("Defence calc engine failed", e);
                    }
                    
                    return 0;
                },
                
                /**
                 * @method {int} getBaseBuildingsComplexityAverage(): get the complexity of the base buildings in average
                 * @param {ClientLib.Data} buildings: the buildings of the current scan object
                 */
                
                getBaseBuildingsComplexityAverage : function( buildings )
                {
                    try {
                        var structure = [];
                        var average   = 0;
                        
                        // Store the buildings coord and ID in array
                        // Set important follow buildings in due array
                        for( var i in buildings )
                        {
                            var struc = buildings[i];
                            var type  = struc.get_Type();
                            var obj   = { type : type,
                                          x    : struc.get_CoordX(),
                                          y    : struc.get_CoordY() };
                            
                            if ( regex.followBuildings.test( type ) )
                            {
                                obj.ub  = 0;
                                obj.pu  = 0;
                                obj.min = obj.x > 0 ? obj.x - 1 : 0;
                                obj.max = obj.y < 8 ? obj.y + 1 : 8;
                                
                                this.follow.push( obj );
                                continue;
                            }
                            
                            structure.push( obj );
                        }
                        
                        // Check the position of the follow buildings compare to original buildings
                        for( var b in structure ) {
                            var i = structure[b];
                
                            for( var f in this.follow ) {
                                var fi = this.follow[f];
                
                                if ( i.y >= fi.y && ! ( i.x > ( fi.x + 1 ) || i.x < ( fi.x - 1 ) ) ) {
                                    fi.ub++;
                                }
                            }
                        }
                
                        // Get the avarage of the units
                        for( var u in this.follow ) {
                            var ub = this.follow[u].ub;
                
                            average += Math.abs( ( ub > 0 ? Math.round( ( ub / get.define.maxBuildingDestroy ) * 100 ) : 0 ) - 100 );
                        }
                
                        return Math.round( average / this.follow.length );
                    } catch (e) {
                        throw Error("Buildings calc engine failed", e);
                    }
                    
                    return 0;
                },
                
                /**
                 * @method {void} getBaseManager(): get base manager
                 * @param {int} scanX: coord x
                 * @param {int} scanY: coord y
                 */
                
                getBaseManager : function(x, y) {
                    try {
                        var getBase = this.getObjectFromCoord(x, y);
                        
                        if ( ! getBase ) {
                            return false;
                        }
                        
                        return get.cities().GetCity( getBase.getID() );
                    }
                    catch (e) {}
                    
                    return null;
                },
                
                /**
                 * @method {void} stopScanningProgress(): stop scanning loop
                 */
                
                stopScanningProgress : function() {
                    this.stopScanning = true;
                }
            }
            
            // Create city clicks events
            var _layoutGrid = null;
            var _webFront = null;
            var _label = null;
            var _cache = {};
            
            get.citryEvents.forEach( function( value )
            {
                var regStatus = "RegionNPC" + value + "StatusInfo";
                var optValue  = "__OPTM_" + value;
                var region    = webfrontend.gui.region[ regStatus ].prototype;
                
                if( ! region[ optValue ] ) {
                    region[ optValue ] = region.onCitiesChange;
                }
                
                region.onCitiesChange = function()
                {
                    try
                    {
                        var selectedCity = ClientLib.Vis.VisMain.GetInstance().get_SelectedObject();

                        if ( selectedCity !== null )
                        {
                            // save the city ID
                            var cityID = selectedCity.get_Id();

                            // Remove exists grid
                            if ( _layoutGrid !== null && _webFront !== null ) {
                                try {
                                    _webFront.remove( _layoutGrid );
                                } catch (e) {
                                    ThrowError(e);
                                }
                                
                                _layoutGrid.removeAll();
                            }
                     
                            // Prepend result to the screen
                            var putResultLayer = function( result )
                            {
                                try {
                                    var color, labelText;
                                    
                                    if ( result >= 80 ) {
                                        color = "rgb(67, 224, 58)";
                                    }
                                    else if ( result < 80 && result > 70 ) {
                                        color = "rgb(220, 224, 58)";
                                    }
                                    else {
                                        color = "rgb(224, 58, 58)";
                                    }
                                    
                                    // Place the label text
                                    if ( result > -1 ) {
                                        labelText = get.define.layoutPrecentText.replace( "%d", result );
                                    }
                                    else {
                                        labelText = get.define.layoutFailedText;
                                    }
    
                                    // Add layer layout
                                    _webFront   = webfrontend.gui.region[ regStatus ].getInstance();
                                    _label      = new qx.ui.basic.Label().set({ value: labelText });
                                    _layoutGrid = new qx.ui.container.Composite( new qx.ui.layout.Grid(5, 5) );
                                    
                                    _layoutGrid.setTextColor( color );
                                    _layoutGrid.setFont("bold");
    
                                    _webFront.addAfter( _layoutGrid, _webFront.getChildren()[10] );
                                    _layoutGrid.add( _label, {
                                        row    : 1,
                                        column : 0,
                                        colSpan: 0
                                    });
                                } catch (e) {
                                    ThrowError(e);
                                }
                            };
                                
                            // Wait for base info
                            var timeout_clear  = 0;
                            var loadCityResult = function() {
                                try {
                                    var getCity = get.cities().GetCity( cityID );
                
                                    // Wait for details response
                                    if ( getCity !== null && typeof getCity === "object" && typeof getCity.get_CoordId !== "undefined" && getCity.get_CoordId() > -1 )
                                    {
                                        // Run on the current base coord
                                        require.runOnBaseByCoord( getCity.get_X(), getCity.get_Y(), function( result, base )
                                        {
                                            // Put the result layer in the top position of the base info
                                            putResultLayer( result );
                                            
                                            // Save base in cache
                                            _cache[ cityID ] = result;
                                            
                                            // Break settimeout loop
                                            cityFound = true;
                                        });
                                    }
                                    
                                    // Try again if failed
                                    else if ( timeout_clear < 20 ) {
                                        timeout_clear++;
                                        setTimeout( loadCityResult, 100 );
                                    }
                                } catch (e) {
                                    ThrowError(e);
                                }
                            };
                            
                            // Check old cache array
                            if ( _cache.hasOwnProperty( cityID ) && typeof _cache[ cityID ] === "number" ) {
                                putResultLayer( _cache[ cityID ] );
                            }
                            else {
                                setTimeout( loadCityResult, 100 );
                            }
                        }
                    }
                    catch (e) {
                        ThrowError("test, " + e);
                    }
                    
                    return this[ optValue ]();
                };
            });
        };
        
        var waitToInterface = function() {
            try {
                if ( typeof ClientLib !== "undefined" && typeof qx != 'undefined' && qx.core.Init.getApplication() ) {
                    initCATool();
                }
                else {
                    setTimeout( waitToInterface, 1000 );
                }
            } catch (e) {
                console.log("CATool Error: ", e);
            }
        };
        
        setTimeout( waitToInterface, 1000 );
    };
    
    try {
        var appendScript;
        
        appendScript = document.createElement("script");
        appendScript.innerHTML = "(" + chancesOfAttackTool.toString() + ")()";
        appendScript.type = "text/javascript";
        
        document.getElementsByTagName("head")[0].appendChild( appendScript );
    } catch (e) {
        console.log("CATool: ", e);
    }
})();