BulkAtk

Just use auto scout instead ?????

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/399263/787093/BulkAtk.js

/**************************** Bulk Attack ****************************************/
// @tabversion 20180613

Tabs.BulkAttack = {
	tabLabel: 'Attack List',
	tabOrder: 2080,
	tabColor : 'brown',
	timer: null,
	cityreason : '',
	Options: {
		AttackCity: 0,
		SkipErrors: true,
		Attack:false,
		AttackPreset:false,
		AllCities:false,
		ClosestCity:false,
		QuickScout:false,
		CoordList:[],
		On:false,
		Toggle : false,
		KnightPriority : 0, // 0 - highest combat (default), 1 - lowest combat, 2 - highest experience, 3 - lowest experience, 4 - no knight
		SendChamp : 0, // 0 - never (default), 1 - always, 2 - if available
		FreeRallySlots : 2,
		intervalSecs  : 5,
		OverrideAP : false,
	},
	myDiv: null,

	init: function(div) {
		var t = Tabs.BulkAttack;

		t.myDiv = div;

		if (!Options.BulkAttackOptions) {
			Options.BulkAttackOptions = t.Options;
		}
		else {
			for (var y in t.Options) {
				if (!Options.BulkAttackOptions.hasOwnProperty(y)) {
					Options.BulkAttackOptions[y] = t.Options[y];
				}
			}
		}

		if (Options.BulkAttackOptions.Toggle) AddSubTabLink('BulkAttack',t.e_toggleswitch, 'BulkAttackToggleTab');
		SetToggleButtonState('BulkAttack',Options.BulkAttackOptions.On,'BulkAttack');

		// strip out any co-ords not ticked - this will be unpopular, but it will avoid the list endlessly growing!

		var b = Options.BulkAttackOptions.CoordList.length;
		while (b--) {
			if (!Options.BulkAttackOptions.CoordList[b].chk) {
				Options.BulkAttackOptions.CoordList.splice(b,1);
			}
		}

		uWExportFunction('btRemoveBulkAttackList', Tabs.BulkAttack.RemoveEntry);
		uWExportFunction('pbattackclick', Tabs.BulkAttack.ToggleEntry);

		// start autoattack loop timer to start in 10 seconds...

		if (Options.BulkAttackOptions.On) {
			t.timer = setTimeout(function () { t.doAutoLoop(Options.BulkAttackOptions.AttackCity);}, (10 * 1000));
		}
	},

	show: function (init) {
		var t = Tabs.BulkAttack;
		if (!Options.BulkAttackOptions.On) {  // reset to current city on show if not already attacking
			if (init) {
				Options.BulkAttackOptions.AttackCity = Cities.byID[InitialCityId].idx;
			}
			else {
				Options.BulkAttackOptions.AttackCity = Cities.byID[uW.currentcityid].idx;
			}
		}

		var m = '<DIV class=divHeader align=center>'+translate('BULK ATTACK LIST')+'</div><div align=center>';
		m += '<table width=100% class=xtab><tr><td width=30%><INPUT id=btBulkAttackToggle type=checkbox '+ (Options.BulkAttackOptions.Toggle?'CHECKED ':'') +'/>&nbsp;'+translate("Add toggle button to main screen header")+'</td><td colspan=2 align=center><INPUT id=BulkAttackButton type=submit value="'+translate("BulkAttack")+' = '+ (Options.BulkAttackOptions.On?'ON':'OFF')+'"></td><td width=30% align=right>&nbsp;</td></tr></table>';
		m += '<br></div><DIV class=divHeader align=center>'+translate('OPTIONS')+'</div><br>';
		m += '<TABLE width=98% align=center cellpadding=0 cellspacing=0 class=xtab><TR width=50%><td><TABLE cellpadding=1 cellspacing=0 class=xtab>';
		m += '<TR><td colspan=2>'+translate("Keep")+' <INPUT id=btbafreerallyslots type=text size=2 maxlength=2 value="'+Options.BulkAttackOptions.FreeRallySlots+'"\> '+translate("free rally point slots")+'</td></tr>';
		m += '<TR><td colspan=2>'+translate("March Interval")+': <INPUT id=btbamarchinterval type=text size=2 maxlength=2 value="'+Options.BulkAttackOptions.intervalSecs+'"\> '+translate("seconds")+'</td></tr>';
		m += '<TR><td colspan=2>'+translate("March from")+': <span id=pbaAttackCitypick> </span></td></tr>';
		m += '<TR><td><input type=checkbox id="pbaclosest"></td><td>'+translate("or select the closest city")+'</td></tr>';
		m += '<TR><td><input type=checkbox id="pbaskip"></td><td>'+translate("Skip targets when errors occur")+'</td></tr>';
		m += '<TR><td><input type=checkbox id="pbaallcities"></td><td>'+translate("Use all cities (not under Ascension Protection!)")+'</td></tr>';
		m += '<TR><td><input type=checkbox id="pbaoverrideap"></td><td>'+tx("Allow marches from cities under Ascension Protection")+'&nbsp;<span class=boldRed>('+tx('BEWARE!')+')</span></td></tr>';
		m += '<TR><td><input type=checkbox id="pbaquick"></td><td>'+translate("Fetch march target details (QuickScout)")+'</td></tr>';
		m += '<TR><td style="height:20px;">&nbsp;</td><td style="vertical-align:bottom;">'+translate("Attack Preset")+'&nbsp;<span id=pbapresetspan>';
		var MarchPresets = {0:"-- Select --"};
		for (var PN in Options.QuickMarchOptions.MarchPresets) {
			MarchPresets[PN] = Options.QuickMarchOptions.MarchPresets[PN][0];
		}
		m += htmlSelector(MarchPresets, Options.BulkAttackOptions.AttackPreset, 'id=pbaattackpreset class=btInput style="vertical-align:text-bottom;"');
		m += '</span></div></td></tr>';
		m += '<TR><td style="height:20px;">&nbsp;</td><td id=pbaknightcell>'+translate('Knight priority')+':&nbsp;'+htmlSelector({0:tx('Highest Combat Skill'), 1:tx('Lowest Combat Skill'), 2:tx('Highest Experience'), 3:tx('Lowest Experience'), 4:tx('No Knight! (Megaliths)')}, Options.BulkAttackOptions.KnightPriority, ' class=btInput id=pbaknight')+'</td></tr>';
		m += '<TR><td style="height:20px;">&nbsp;</td><td id=pbachampcell>'+translate('Send Champion')+':&nbsp;'+htmlSelector({0:translate('Never'), 1:translate('Always'), 2:translate('If Available')}, Options.BulkAttackOptions.SendChamp, ' class=btInput id=pbachamp')+'</td></tr></table>';

        m += '</td><td width=50%>';
        m += '<DIV>'+translate("Co-ordinates")+':</div>';
        m += '<DIV><textarea id=pbBulkAttackcoords rows=7 cols=40 onkeyup="ptStopProp(event);" title="Separate multiple co-ordinates with spaces.\nValid formats include xxx,yyy (xxx_yyy) [xxx.yyy] etc..."></textarea></div>';
		m += '<DIV>'+strButton20('Add to Attack List', 'id=pbAddBulkAttack')+'</div>';
        m += '</td></tr><tr><td height=20 id=pbBulkAttackmsg align=center colspan=2>&nbsp;</td></tr></table>';

		m += '</div><DIV class=divHeader align=center>'+translate('ATTACK QUEUE')+'</div><br>';
        m += '<DIV id=btBulkAttackList style="height:220px; overflow-y:auto;"></div><br>';

		t.myDiv.innerHTML = m;
		t.PaintList('');

		new CdispCityPicker ('pbBulkAttackPick', ById('pbaAttackCitypick'), true, function(c,x,y){ Options.BulkAttackOptions.AttackCity = c.idx; }, Options.BulkAttackOptions.AttackCity);

		ById('BulkAttackButton').addEventListener('click', function() {
			t.e_toggleswitch(this)
		}, false);

		ById('btBulkAttackToggle').addEventListener ('change', function() {
			Options.BulkAttackOptions.Toggle = this.checked;
			saveOptions();
        }, false);

		ById('btbafreerallyslots').addEventListener ('change', function(e){
			if (isNaN(e.target.value)) { e.target.value = 0; }
			Options.BulkAttackOptions.FreeRallySlots = parseIntNan(e.target.value);
            saveOptions ();
		},false);

		ById('btbamarchinterval').addEventListener('keyup', function () {
			if (parseIntNan(ById('btbamarchinterval').value)<1) { ById('btbamarchinterval').value = 5; }
			if (parseIntNan(ById('btbamarchinterval').value)<2) { ById('btbamarchinterval').value = 2; }
			Options.BulkAttackOptions.intervalSecs = parseIntNan(ById('btbamarchinterval').value);
			saveOptions();
		}, false);

		ToggleOption('BulkAttackOptions','pbaclosest','ClosestCity');
		ToggleOption('BulkAttackOptions','pbaskip','SkipErrors');
		ToggleOption('BulkAttackOptions','pbaallcities','AllCities');
		ToggleOption('BulkAttackOptions','pbaoverrideap','OverrideAP');
		ToggleOption('BulkAttackOptions','pbaquick','QuickScout');

		ById('pbAddBulkAttack').addEventListener('click', t.AddCoords, false);

		ById('pbaattackpreset').addEventListener('change', function() {
			Options.BulkAttackOptions.AttackPreset = ById('pbaattackpreset').value;
			saveOptions();
		}, false);

		ById('pbaknight').addEventListener('change', function(){
			Options.BulkAttackOptions.KnightPriority = ById('pbaknight').value;
			saveOptions();
		}, false);

		ById('pbachamp').addEventListener('change', function(){
			Options.BulkAttackOptions.SendChamp = ById('pbachamp').value;
			saveOptions();
		}, false);
    },

	hide: function () { },

	e_toggleswitch: function(obj) {
		var t = Tabs.BulkAttack;
		obj = ById('BulkAttackButton');
		if (Options.BulkAttackOptions.On) {
			if (obj) obj.value = "BulkAttack = OFF";
			Options.BulkAttackOptions.On = false;
		} else {
			if (obj) obj.value = "BulkAttack = ON";
			Options.BulkAttackOptions.On = true;
			t.timer = setTimeout(function () { t.doAutoLoop(Options.BulkAttackOptions.AttackCity);}, 0);
		}
		saveOptions();
		SetToggleButtonState('BulkAttack',Options.BulkAttackOptions.On,'BulkAttack');
	},

	PaintList : function (msg) {
		var t = Tabs.BulkAttack;

		var z = '';
		var r = 0;
		var logshow = false;
		var sel = 0;

		var z = '<div align="center">';
		z += '<TABLE width=98% align=center cellpadding=0 cellspacing=0 class=xtab><TR><TD colspan=4 align=right id=pbamarchinfo>&nbsp;</td></tr><tr><TH class=xtabHD width=15><input type=checkbox id=pbamarch_All /></th><TH width=100 class=xtabHD>'+translate('Co-ords')+'</th><th class=xtabHD>'+translate('Details')+'</th><th align=right class=xtabHD>'+strButton14(tx('Export'),'id=btExportBulkAttackList')+'&nbsp;'+strButton14(tx('Clear List'),'id=btClearBulkAttackList')+'</th></tr>';
		for(i = 0; i < Options.BulkAttackOptions.CoordList.length; i++){
			logshow = true;
			r=r+1;
			rowClass = 'evenRow';
			var rem = (r % 2);
			if (rem == 1) rowClass = 'oddRow';

			z += '<TR class="'+rowClass+'"><TD align=center width=15><input type=checkbox name=pbamarchchk id="pbamarchchk_'+Options.BulkAttackOptions.CoordList[i].x+'_'+Options.BulkAttackOptions.CoordList[i].y+'" value="'+Options.BulkAttackOptions.CoordList[i].x+'_'+Options.BulkAttackOptions.CoordList[i].y+'" '+(Options.BulkAttackOptions.CoordList[i].chk?'CHECKED':'')+' onclick="pbattackclick(\''+Options.BulkAttackOptions.CoordList[i].x+'_'+Options.BulkAttackOptions.CoordList[i].y+'\')" /></td><TD align=center>'+coordLink(Options.BulkAttackOptions.CoordList[i].x,Options.BulkAttackOptions.CoordList[i].y)+'</td><TD align=left id="pbamarchdetails_'+Options.BulkAttackOptions.CoordList[i].x+'_'+Options.BulkAttackOptions.CoordList[i].y+'">'+(Options.BulkAttackOptions.CoordList[i].Details?Options.BulkAttackOptions.CoordList[i].Details:'')+'<td align=right><a id="pbamarchdelete'+Options.BulkAttackOptions.CoordList[i].x+'_'+Options.BulkAttackOptions.CoordList[i].y+'" class="inlineButton btButton brown8" onclick="btRemoveBulkAttackList(\''+Options.BulkAttackOptions.CoordList[i].x+'_'+Options.BulkAttackOptions.CoordList[i].y+'\')"><span>Remove</span></a></td></tr>';

			if (Options.BulkAttackOptions.CoordList[i].chk) sel++;
		}

		if (!logshow) {
			z += '<tr><td colspan=4 class=xtab><div align="center"><br><br>No list entries</div></td></tr>';
		}

		z += '</table></div>';

		ById('btBulkAttackList').innerHTML = z;
		ById('pbBulkAttackmsg').innerHTML = msg;
		ById('pbamarchinfo').innerHTML = '('+sel+'/'+Options.BulkAttackOptions.CoordList.length+')';
		ById('btClearBulkAttackList').addEventListener ('click', function() {t.ClearList();}, false);
		ById('btExportBulkAttackList').addEventListener ('click', function() {t.ExportList();}, false);

		ById('pbamarch_All').addEventListener('change', function(){
			var sel = 0;
			for(k in document.getElementsByName('pbamarchchk'))
				document.getElementsByName('pbamarchchk')[k].checked = ById('pbamarch_All').checked;
			for (var b in Options.BulkAttackOptions.CoordList) {
				Options.BulkAttackOptions.CoordList[b].chk = ById('pbamarch_All').checked;
				if (Options.BulkAttackOptions.CoordList[b].chk) sel++;
			}
			saveOptions();
			ById('pbamarchinfo').innerHTML = '('+sel+'/'+Options.BulkAttackOptions.CoordList.length+')';
		}, false);
	},

	ClearList : function () {
		var t = Tabs.BulkAttack;
		Options.BulkAttackOptions.CoordList = [];
		saveOptions();
		t.PaintList('Attack List Cleared');
	},

	ExportList : function () {
		var t = Tabs.BulkAttack;
		var CoordList = [];
		for(i = 0; i < Options.BulkAttackOptions.CoordList.length; i++){
			CoordList.push('('+Options.BulkAttackOptions.CoordList[i].x+','+Options.BulkAttackOptions.CoordList[i].y+')');
		}
		if (CoordList.length>0) {
			window.prompt(tx("Copy to clipboard: Ctrl+C"), CoordList.join(" "));
		}
	},

	RemoveEntry : function (c) {
		var t = Tabs.BulkAttack;
		var Coord = [];
		Coord = c.split("_");
		for (var b in Options.BulkAttackOptions.CoordList) {
			if (Options.BulkAttackOptions.CoordList[b].x==Coord[0] && Options.BulkAttackOptions.CoordList[b].y==Coord[1]) {
				Options.BulkAttackOptions.CoordList.splice(b,1);
				break;
			}
		}
		saveOptions();
		t.PaintList('Entry deleted');
	},

	ToggleEntry : function (c) {
		var t = Tabs.BulkAttack;
		var Coord = [];
		Coord = c.split("_");
		var sel = 0;
		for (var b in Options.BulkAttackOptions.CoordList) {
			if (Options.BulkAttackOptions.CoordList[b].x==Coord[0] && Options.BulkAttackOptions.CoordList[b].y==Coord[1]) {
				Options.BulkAttackOptions.CoordList[b].chk = !Options.BulkAttackOptions.CoordList[b].chk;
				saveOptions();
			}
			if (Options.BulkAttackOptions.CoordList[b].chk) sel++;
		}
		ById('pbamarchinfo').innerHTML = '('+sel+'/'+Options.BulkAttackOptions.CoordList.length+')';
	},

	UnselectEntry : function (x,y) {
		var t = Tabs.BulkAttack;
		for (var b in Options.BulkAttackOptions.CoordList) {
			if (Options.BulkAttackOptions.CoordList[b].x==x && Options.BulkAttackOptions.CoordList[b].y==y) {
				Options.BulkAttackOptions.CoordList[b].chk = false;
				saveOptions();
			}
		}
		if (ById('pbamarchchk_'+x+'_'+y)) {
			ById('pbamarchchk_'+x+'_'+y).checked = false;
		}
	},

	AddCoords : function () {
		var t = Tabs.BulkAttack;

		var NewCoords = ById('pbBulkAttackcoords').value;
		NewCoords = replaceAll(NewCoords,"(", " ");
		NewCoords = replaceAll(NewCoords,")", " ");
		NewCoords = replaceAll(NewCoords,"[", " ");
		NewCoords = replaceAll(NewCoords,"]", " ");
		NewCoords = replaceAll(NewCoords,"_", ",");
		NewCoords = replaceAll(NewCoords,".", ",");
		var NewCoordList = [];
		var CleanedCoordList = [];
		var Coord = [];
		var ListEntry = new Object();
		var msg = '';

		if (NewCoords.trim() != "") {
			NewCoordList = NewCoords.split(" ");
		}
		CoordError = false;
		CoordsAdded = false;
		for (var a=0;a<NewCoordList.length;a++) {
			var c = NewCoordList[a];
			if (c.trim() != "") {
				Coord = c.split(",");
				if (Coord[0] && !isNaN(Coord[0]) && Coord[1] && !isNaN(Coord[1])) {
					// look like coords?
					CleanedCoordList.push({x:Coord[0],y:Coord[1]});
					// avoid duplicates by deleting existing entry for these coords
					for (var b=0;b<Options.BulkAttackOptions.CoordList.length;b++) {
						if (Options.BulkAttackOptions.CoordList[b].x==Coord[0] && Options.BulkAttackOptions.CoordList[b].y==Coord[1]) {
							Options.BulkAttackOptions.CoordList.splice(b,1);
							break;
						}
					}
				}
				else {
					CoordError = true;
				}
			}
		}

		if (CoordError) {
			msg = '<span style="color:#800;">Invalid format!</span>';
		}
		else {
			for (var a=0;a<CleanedCoordList.length;a++) {
				CoordsAdded = true;
				ListEntry = {};
				ListEntry.chk = true;
				ListEntry.x = CleanedCoordList[a].x;
				ListEntry.y = CleanedCoordList[a].y;
				ListEntry.details = '';
				Options.BulkAttackOptions.CoordList.push(ListEntry);
			}
			if (CoordsAdded) msg = 'Co-ordinates added';
			ById('pbBulkAttackcoords').value = '';
		}
		saveOptions();
		t.PaintList(msg);
	},

	ImportCoords : function(CoordList) {
		var t = Tabs.BulkAttack;
		CoordsAdded = false;
		for (var a=0;a<CoordList.length;a++) {
			var c = CoordList[a];
			if (c.trim() != "") {
				Coord = c.split(",");
				for (var b=0;b<Options.BulkAttackOptions.CoordList.length;b++) {
					if (Options.BulkAttackOptions.CoordList[b].x==Coord[0] && Options.BulkAttackOptions.CoordList[b].y==Coord[1]) {
						Options.BulkAttackOptions.CoordList.splice(b,1);
						break;
					}
				}
				ListEntry = {};
				ListEntry.chk = true;
				ListEntry.x = Coord[0];
				ListEntry.y = Coord[1];
				ListEntry.details = '';
				Options.BulkAttackOptions.CoordList.push(ListEntry);
				CoordsAdded = true;
			}
		}

		if (CoordsAdded) { ById('bttcBulkAttack').click(); }
	},

	doAutoLoop : function (idx) {
		var t = Tabs.BulkAttack;
		clearTimeout(t.timer);
		if (ById('pbBulkAttackmsg')) { ById('pbBulkAttackmsg').innerHTML = ''; }

		var cityId = Cities.cities[idx].id;

		// get next attack entry, if none, then switch off.

		var entry = null;
		for(var i = 0; i < Options.BulkAttackOptions.CoordList.length; i++){
			if (Options.BulkAttackOptions.CoordList[i].chk) {
				entry = Options.BulkAttackOptions.CoordList[i];
				break;
			}
		}

		if (!entry) {
			Options.BulkAttackOptions.On = false;
			saveOptions();
			if (ById('BulkAttackButton')) { ById('BulkAttackButton').value = "BulkAttack = OFF"; }
			SetToggleButtonState('BulkAttack',Options.BulkAttackOptions.On,'BulkAttack');
			t.PaintList(translate('Attacking Completed'));
			return;
		}
		else {
			if (!Options.BulkAttackOptions.On) {
				t.UpdateDetails(entry.x,entry.y,translate('Attacking Cancelled'));
				return;
			}
			if (!Options.BulkAttackOptions.AttackPreset || Options.BulkAttackOptions.AttackPreset=="0") {
				t.UpdateDetails(entry.x,entry.y,translate('No Attack Preset'));
				return;
			}
			t.UpdateDetails(entry.x,entry.y,translate('Sending')+'...');
		}

		if (Options.BulkAttackOptions.ClosestCity) { // select closest city
			var idx = t.SelectClosest(entry.x,entry.y);
			var cityId = Cities.cities[idx].id;
		}

		// check currently selected city is suitable for the march.

		var citysuitable = t.CheckCitySuitable(cityId,true);

		if (!citysuitable) {
			if (Options.BulkAttackOptions.AllCities) { // check other cities
				var newidx = t.GetNextSuitableCity(idx);
				if (newidx!=idx) {
					idx = newidx;
					var cityId = Cities.cities[idx].id;
					citysuitable=true;
					actionLog('Changing city to ' + Cities.cities[idx].name,'BULKATTACK')
				}
			}
		}

		if (!citysuitable) {
			t.UpdateDetails(entry.x,entry.y,t.cityreason+'...',true);
			actionLog(t.cityreason,'BULKATTACK')
			// 1 min delay... no suitable cities at the moment...
			if (!Options.BulkAttackOptions.ClosestCity || Options.BulkAttackOptions.AllCities) {
				t.timer = setTimeout(function () { t.doAutoLoop(Options.BulkAttackOptions.AttackCity);}, (60 * 1000));
			}
			else {
				// move co-ords to end of list and try next, because the next entry could be for another city
				Options.BulkAttackOptions.CoordList.push(Options.BulkAttackOptions.CoordList.splice(i,1)[0]);
				t.timer = setTimeout(function () { t.doAutoLoop(idx);}, (Options.BulkAttackOptions.intervalSecs * 1000));
			}
			return;
		}

		// Send the attack, and loop back once sent...

		t.sendAttack(entry.x, entry.y, cityId, function () { var t = Tabs.BulkAttack; t.timer = setTimeout(function () { t.doAutoLoop(idx); }, (Options.BulkAttackOptions.intervalSecs * 1000)); });
	},

	SelectClosest : function (x2,y2) {
		var t = Tabs.BulkAttack;
		var closestdist = 999999;
		var closestcity;

		if(isNaN(x2) || isNaN(y2)) return;

		for (var i = 0; i < Cities.numCities; i++) {
			var	cityId = Cities.cities[i].id;
			var ascensionok = (!uW.cm.PrestigeCityPlayerProtectionController.isActive(cityId) || Options.BulkAttackOptions.OverrideAP); // don't select city under AP!
			if (ascensionok) {
				var x1 = parseInt(Cities.cities[i].x);
				var y1 = parseInt(Cities.cities[i].y);
				if (x1 != x2 || y1 != y2) { // if one of your cities, pick the nearest other city!
					var dist = distance(x1, y1, x2, y2);
					if (dist < closestdist) {
						closestdist = dist;
						closestcity = i;
					}
				}
			}
		}
		return closestcity;
	},

	GetNextSuitableCity : function (idx) {
		var t = Tabs.BulkAttack;
		var oldidx = idx;
		do {
			idx++;
			if (idx >= Number(Cities.numCities)) idx = 0;
			cityId = Cities.cities[idx].id;
		}
		while (!t.CheckCitySuitable(cityId) && (idx != oldidx))
		return idx;
	},

	CheckCitySuitable : function (cityId,reason) {
		var t = Tabs.BulkAttack;

		var troopsok = true;
		var CheckArray = [];
		for (var ui in uW.cm.UNIT_TYPES) {
			var i = uW.cm.UNIT_TYPES[ui];
			if (Options.QuickMarchOptions.MarchPresets[Options.BulkAttackOptions.AttackPreset][i]) {
				CheckArray[i] = parseIntNan(Options.QuickMarchOptions.MarchPresets[Options.BulkAttackOptions.AttackPreset][i]);
			}
		}
		for (var ui in uW.cm.UNIT_TYPES) {
			var i = uW.cm.UNIT_TYPES[ui];
			if (CheckArray[i] && CheckArray[i] > parseIntNan(Seed.units['city' + cityId]['unt'+i])) {
				troopsok = false;
				break;
			}
		}

		var knightok = true;
		var knt = getAvailableKnights(cityId);
		if (!knt[0]) { knightok = false; }

		var marches = parseIntNan(March.getMarchSlots(cityId));
		var maxmarches = parseIntNan(March.getTotalSlots(cityId));
		var keepfree = Number(Options.BulkAttackOptions.FreeRallySlots); // use highest of bulk attack keep rally free or general keep rally free
		if (keepfree < Number(Options.FreeRallySlots)) { keepfree = Number(Options.FreeRallySlots); }
		var rallyok = ((marches+keepfree) < maxmarches);
		var towerok = (!Options.TowerOptions || !Options.TowerOptions.SaveCityState[cityId] || Options.TowerOptions.SaveCityState[cityId].AllowMarches);
		var ascensionok = (!uW.cm.PrestigeCityPlayerProtectionController.isActive(cityId) || Options.BulkAttackOptions.OverrideAP);

		var champok = true;
		if (parseIntNan(Options.BulkAttackOptions.SendChamp)==1) {
			var champ = false;
			for (var c in Seed.champion.champions) {
				citychamp = Seed.champion.champions[c];
				var champstatus = citychamp.status;
				if (citychamp.assignedCity == cityId && champstatus != "10") {
					champ = true;
					break;
				}
			}
			champok = champ;
		}
		if (reason) {
			t.cityreason = translate('Waiting for rally point to clear!');
			if (rallyok) {
				if (!knightok) t.cityreason = translate('Waiting for an available knight!');
				else if (!champok) t.cityreason = translate('No Champion available!');
				else if (!troopsok) t.cityreason = translate('Waiting for available troops!');
				else if (!towerok) t.cityreason = translate('Source city is under attack - waiting for all clear!');
				else if (!ascensionok) t.cityreason = translate('Source city is under ascension protection - cannot march from here!');
			}
		}

		return (troopsok && knightok && rallyok && towerok && ascensionok && champok);
	},

	UpdateDetails : function(x,y,msg,perm) {
		var t = Tabs.BulkAttack;
		var el = 'pbamarchdetails_'+x+'_'+y;
		var elem = ById(el);
		if (elem) { elem.innerHTML = msg; }
		if (perm) {
			for (var b in Options.BulkAttackOptions.CoordList) {
				if (Options.BulkAttackOptions.CoordList[b].x==x && Options.BulkAttackOptions.CoordList[b].y==y) {
					Options.BulkAttackOptions.CoordList[b].Details = msg;
					saveOptions();
				}
			}
		}
	},

	sendAttack : function(x, y, cid, notify){
		var params = uW.Object.clone(uW.g_ajaxparams);
		params.cid = cid;
		params.kid = 0;
		params.xcoord = x;
		params.ycoord = y;
		var knt = getAvailableKnights(cid);
		if (knt[0]) {
			if (Options.BulkAttackOptions.KnightPriority==1) { // lowest combat skill
				knt = knt.sort(function sort(a,b) {a = a['Combat'];b = b['Combat'];return a == b ? 0 : (a < b ? -1 : 1);});
			};
			if (Options.BulkAttackOptions.KnightPriority==2) { // highest experience
				knt = knt.sort(function sort(a,b) {a = a['Experience'];b = b['Experience'];return a == b ? 0 : (a > b ? -1 : 1);});
			};
			if (Options.BulkAttackOptions.KnightPriority==3) { // lowest experience
				knt = knt.sort(function sort(a,b) {a = a['Experience'];b = b['Experience'];return a == b ? 0 : (a < b ? -1 : 1);});
			};
			if (Options.BulkAttackOptions.KnightPriority!=4) { // no knight - megaliths!
				params.kid=knt[0].ID; // will fail if no knights
			}
		}
		params.type = 4;

		for (var ui in uW.cm.UNIT_TYPES) {
			var i = uW.cm.UNIT_TYPES[ui];
			params["u"+i] = 0;
			if (Options.QuickMarchOptions.MarchPresets[Options.BulkAttackOptions.AttackPreset][i]) {
				params["u"+i] = parseIntNan(Options.QuickMarchOptions.MarchPresets[Options.BulkAttackOptions.AttackPreset][i]);
			}
		}

		var iused = new Array();
		for (var i = 0; i < QuickMarch.ItemList.length; i++) {
			if (Options.QuickMarchOptions.MarchPresets[Options.BulkAttackOptions.AttackPreset]["item"+QuickMarch.ItemList[i]] == true && Seed.items["i"+QuickMarch.ItemList[i]]) {
				iused.push(QuickMarch.ItemList[i]);
			}
		}
		params.items = iused.join(",");

		params.gold = 0;
		params.r1 = 0;
		params.r2 = 0;
		params.r3 = 0;
		params.r4 = 0;
		params.r5 = 0;

		params.champid = 0;
		if (parseIntNan(Options.BulkAttackOptions.SendChamp)!=0) {
			for (var c in Seed.champion.champions) {
				citychamp = Seed.champion.champions[c];
				if (citychamp.assignedCity == cid) {
					var champstatus = citychamp.status;
					if (champstatus != "10") { params.champid = citychamp.championId; }
					break;
				}
			}
		}

		March.addMarch(params, function(rslt){
			var t = Tabs.BulkAttack;
			if (rslt.ok) {
				var extrainfo = '';
				if (cid!=Cities.cities[Options.BulkAttackOptions.AttackCity].id || Options.BulkAttackOptions.ClosestCity) { extrainfo += ' from '+Cities.byID[cid].name; }
				t.UpdateDetails(x,y,translate('Attack sent')+extrainfo+'!',true);
				t.UnselectEntry(x,y);
				if (Options.BulkAttackOptions.QuickScout) {
					ChatStuff.fetchmarch(rslt.marchId,t.QuickScoutResults);
				}
			}
			else {
				var msg = translate('March failed to send!');
				if (rslt.msg) msg = rslt.msg;
				if (rslt.error_code == 208 || rslt.error_code == 207 || rslt.error_code == 104) { // will never be able to send
					t.UpdateDetails(x,y,msg);
					t.UnselectEntry(x,y);
					// update search tab if coords exist and it's misted and target it truced..
					if(rslt.error_code == 208) {
						if (Tabs.Search && Tabs.Search.mapDat) {
							var numRows = Tabs.Search.mapDat.length;
							for (var i=0; i<numRows; i++){
								if (Tabs.Search.mapDat[i][0] == x && Tabs.Search.mapDat[i][1] == y) {
									if (Tabs.Search.mapDat[i][13]) {
										Tabs.Search.mapDat[i][6] = 0;
										Tabs.Search.mapDat[i][8] = '<span style="color:#800;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Target is truced - Cannot attack!</span>';
									}
									break;
								}
							}
						}
					}
				}
				else {
					if (Options.BulkAttackOptions.SkipErrors) {
						t.UpdateDetails(x,y,msg,true);
						t.UnselectEntry(x,y);
					} else {
						t.UpdateDetails(x,y,msg+' '+translate('Retrying')+'...',true);
					}
				}
			}
			if (notify) {notify();}
		});
	},

	QuickScoutResults : function (rslt,rslt2,march) {
		var t = Tabs.BulkAttack;

		var a = march;
		var totile = tileTypes[parseInt(a["toTileType"])];
		if (a["toTileType"] == 51) {
			if (!a["toPlayerId"]) { totile = "???"; }
			else { if (a["toPlayerId"] == 0) totile = 'Barb Camp'; }
		}
		totile = 'Lvl '+a["toTileLevel"]+' '+totile;

		if (rslt2 && rslt2.userInfo) {
			u2 = rslt2.userInfo[0];
			var alli2 = 'None';
			if (u2.allianceName)
				alli2 = u2.allianceName + FormatDiplomacy(u2.allianceId);

			t.UpdateDetails(march.toXCoord,march.toYCoord,totile+' - Name: '+PlayerLink(a.toPlayerId,u2.genderAndName)+', Alliance: '+alli2,true);
		}
		else {
			t.UpdateDetails(march.toXCoord,march.toYCoord,totile,true);
		}

		// update misted search if it exists
		if (Tabs.Search && Tabs.Search.mapDat) {
			var numRows = Tabs.Search.mapDat.length;
			for (var i=0; i<numRows; i++){
				if (Tabs.Search.mapDat[i][0] == march.toXCoord && Tabs.Search.mapDat[i][1] == march.toYCoord) {
					if (Tabs.Search.mapDat[i][13]) {
						if (!rslt2) {
							QuickScout.FillSearchDiv({errorMsg:"plain"},march);
						}
						else {
							QuickScout.FillSearchDiv(rslt2,march);
						}
					}
					break;
				}
			}
		}
	},
}