Three Kingdoms Autoplayer

By erik a. Last update Jan 20, 2010 — Installed 7,266 times.

There are 44 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name           Three Kingdoms Autoplayer
// @namespace      erik
// @description    Auto player for KoramGame Three Kingdoms game
// @include        http*://s*.tk.koramgame.com/
// @version	   56
// ==/UserScript==

if(!GM_log) {
	GM_log=console.debug;
}

var thisVersion = 56;
var debug=false;
var newVersionAvailable=0;

if (parseInt(GM_getValue('SUC_remote_version',0)) > thisVersion) {
	newVersionAvailable=1;
}

// update script from: http://userscripts.org/scripts/review/65460
var SUC_script_num = 65460;
try{ function updateCheck(forced) {
	if ((forced) || (parseInt(GM_getValue('SUC_last_update', '0')) + (86400000*1) <= (new Date().getTime()))) {
		try {
			GM_xmlhttpRequest({
				method: 'GET',
				url: 'http://userscripts.org/scripts/source/'+SUC_script_num+'.meta.js?'+new Date().getTime(),
				headers: {'Cache-Control': 'no-cache'},
				onload: function(resp){
					var remote_version, rt, script_name;
					rt=resp.responseText;
					GM_setValue('SUC_last_update', new Date().getTime()+'');
					remote_version=parseInt(/@version\s*(.*?)\s*$/m.exec(rt)[1]);
					script_name = (/@name\s*(.*?)\s*$/m.exec(rt))[1];
					GM_setValue('SUC_target_script_name', script_name);
					GM_setValue('SUC_remote_version', remote_version);
					GM_log('remote version ' + remote_version);
					if (remote_version > thisVersion) {
						newVersionAvailable=1;
						if (forced) {
							if(confirm('There is an update available for the Greasemonkey script "'+script_name+'."\nWould you like to go to the install page now?')) {
								GM_openInTab('http://userscripts.org/scripts/show/'+SUC_script_num);
							}
						}
					} else if (forced) alert('No update is available for "'+script_name+'."');
				}
			})
		}catch (err) {
			if (forced) alert('An error occurred while checking for updates:\n'+err);
		}
	}
     }
     GM_registerMenuCommand(GM_getValue('SUC_target_script_name', '???') + ' - Manual Update Check', function(){updateCheck(true);});
     updateCheck(false);
} catch(err) {}

window.addEventListener("load", function(e) {
	nHtml.clearTimeouts();
	Player.ReloadOccasionally();
	Player.MainLoop();
},false);

String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); }


/////////////////////////////////////////////////////////////////////
//							HTML TOOLS
// this object contains optionally prefix'ed GM_ replacements
// useful if possible to play multiple servers, worlds, games, etc.
/////////////////////////////////////////////////////////////////////

var GM={
setLValue:function(k, v) {
	GM_setValue(this.lprefix + k, v)
},
getLValue:function(k, d) {
	return GM_getValue(this.lprefix + k, d)
},
setGValue:function(k, v) {
	GM_setValue(this.gprefix + k, v)
},
getGValue:function(k, d) {
	return GM_getValue(this.gprefix + k, d)
},
log:function(m) {
	GM_log(this.lprefix + m)
},
debug:function(m) {
	if (this.getGValue('DebugMode'))
		GM_log(this.lprefix + m);
},
gprefix:'',
lprefix:''
};

var tk_server = String(window.location.hostname).match(/\d+/);
if (tk_server) {
	tk_server=parseInt(tk_server);
	GM.gprefix = GM.lprefix = tk_server + '.';
}

/////////////////////////////////////////////////////////////////////
//							HTML TOOLS
// this object contains general methods for wading through the DOM and dealing with HTML
/////////////////////////////////////////////////////////////////////

var nHtml={
InCList:function(clist, n) {
// returns i>=0 if n starts with the text in one of the comma-delimited entries in clist
// case and whitespace insensitive search
// return -1 if no match
	if (!clist || clist=='') return -1;

	n=n.trim().toLowerCase();
	if (!n || n=='') return -1;
	
	var list = clist.split(',');
	for(var index in list) {
		var e=list[index].trim().toLowerCase();
		if (e=='') next;
		if (n.indexOf(e)==0) return index;
	}
	return -1;
},
Dump:function(arr,level) {
	var dumped_text = "";
	if(!level) level = 0;
	
	//The padding given at the beginning of the line.
	var level_padding = "";
	for(var j=0;j<level+1;j++) level_padding += "    ";
	
	if(typeof(arr) == 'object') { //Array/Hashes/Objects 
		for(var item in arr) {
			var value = arr[item];
			
			if(typeof(value) == 'object') { //If it is an array,
				dumped_text += level_padding + "'" + item + "' ...\n";
				dumped_text += dump(value,level+1);
			} else {
				dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
			}
		}
	} else { //Stings/Chars/Numbers etc.
		dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
	}
	return dumped_text;
},
FindByAttrContains:function(obj,tag,attr,className) {
	var pre='@';
	className=className.toLowerCase();
	if(attr=="className") { attr="class"; }
	if(attr=="text()") { pre=''; }
	try {
		var q=document.evaluate(".//"+tag+
			"[contains(translate("+pre+attr+",'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),'"+className+
			"')]",obj,null,
			XPathResult.FIRST_ORDERED_NODE_TYPE,null);
	} catch (err) {
		GM.log("XPath Failed:"+xpath+","+err);
	}
	if(q && q.singleNodeValue) { return q.singleNodeValue; }
	return null;
},
FindByAttrXPath:function(obj,tag,className) {
	var q=null;
	try {
		var xpath=".//"+tag+"["+className+"]";
		if(obj==null) {
			GM.log('Trying to find xpath with null obj:'+xpath);
			return null;
		}
		q=document.evaluate(xpath,obj,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null);
	} catch(err) {
		GM.log("XPath Failed:"+xpath+","+err);
	}
	if(q && q.singleNodeValue) { return q.singleNodeValue; }
	return null;
},
LoopByXPath:function(obj,xpath,snapFunc) {
	var ss;
	try {
		ss=document.evaluate(xpath,obj,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
	} catch(err) {
		GM.log("XPath Failed:"+xpath+","+err);
	}
	if (ss) {
	for(var s=0; s<ss.snapshotLength; s++) {
		if (snapFunc(ss.snapshotItem(s)) == 'break') {
			break;
		}
	}
	} else {
		GM.log("XPath didn't find anything:"+xpath);
	}
},
FindByXPath:function(obj,xpath) {
	var q=null;
	try {
		if(obj==null) {
			GM.log('Trying to find xpath with null obj:'+xpath);
			return null;
		}
		q=document.evaluate(xpath,obj,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null);
	} catch(err) {
		GM.log("XPath Failed:"+xpath+","+err);
	}
	if(q && q.singleNodeValue) { return q.singleNodeValue; }
	return null;
},
FindByAttr:function(obj,tag,attr,className) {
	if(className.exec==undefined) {
		if(attr=="className") { attr="class"; }
		try {
			var q=document.evaluate(".//"+tag+"[@"+attr+"='"+className+"']",obj,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null);
			if(q && q.singleNodeValue) { return q.singleNodeValue; }
		} catch (err) {
			GM.log("XPath Failed:"+xpath+","+err);
		}
		return null;
	}
	var divs=obj.getElementsByTagName(tag);
	for(var d=0; d<divs.length; d++) {
		var div=divs[d];
		if(className.exec!=undefined) {
			if(className.exec(div[attr])) {
				return div;
			}
		} else if(div[attr]==className) {
			return div;
		}
	}
	return null;
},
FindByTag:function(obj,tag) {
	try {
		var q=document.evaluate(".//"+tag,obj,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null);
	} catch(err) {
		GM.log("XPath Failed:"+xpath+","+err);
	}
	if(q && q.singleNodeValue) { return q.singleNodeValue; }
	return null;
},
FindByClassName:function(obj,tag,className) {
	return this.FindByAttr(obj,tag,"className",className);
},
Click2:function(obj,evtName) {
	var evt = document.createEvent("MouseEvents");
	evt.initMouseEvent(evtName, true, true, window,
		0, 0, 0, 0, 0, false, false, false, false, 0, null);
	return !obj.dispatchEvent(evt);
},
ClickNoWait:function(obj) {
//	this.Click2(obj,"mousedown");
//	this.Click2(obj,"mouseup");
	this.Click2(obj,"click");
},
Click:function(obj) {
	this.setTimeout(function() {
		if (!obj.getAttribute('onclick')) {
			if (obj.href && obj.href.match(/^javascript:/)) {
				var code = obj.href.substr(11);
				if (code.indexOf('void(0)') < 0) {
					//GM.debug("Changing " + obj.getAttribute('onclick') + " to " + obj.href.substr(11));
					obj.setAttribute('onclick', unescape(obj.href.substr(11)));
				}
			}
		}
		nHtml.ClickNoWait(obj);
	},1000+Math.floor(Math.random()*1000));
},
spaceTags:{
	'td':1,
	'br':1,
	'hr':1,
	'span':1,
	'table':1
},
GetText:function(obj) {
	var txt=' ';
	if(obj.tagName!=undefined && this.spaceTags[obj.tagName.toLowerCase()]) {
		txt+=" ";
	}
	if(obj.nodeName=="#text") { return txt+obj.textContent; }
	for(var o=0; o<obj.childNodes.length; o++) {
		var child=obj.childNodes[o];
		txt+=this.GetText(child);
	}
	return txt;
},

htmlRe:new RegExp('<[^>]+>','g'),
StripHtml:function(html) {
	return html.replace(this.htmlRe,'').replace(/&nbsp;/g,'');
},

timeouts:{},
setTimeout:function(func,millis) {
	var t=window.setTimeout(function() {
		func();
		nHtml.timeouts[t]=undefined;
	},millis);
	this.timeouts[t]=1;
},
clearTimeouts:function() {
	for(var t in this.timeouts) {
		window.clearTimeout(t);
	}
	this.timeouts={};
},
IsVisible:function(n) {
	return n && n.visibility!='hidden' && n.style.display!='none' && (!n.parentNode || n.parentNode.tagName=='BODY' || this.IsVisible(n.parentNode));
}
};

/////////////////////////////////////////////////////////////////////

//							Player OBJECT

// this is the main object for the game, containing all methods, globals, etc.

/////////////////////////////////////////////////////////////////////

Player={

CheckForImage:function(image,webSlice) {
	if (!webSlice) {
		webSlice=document.body;
	}
	if (imageSlice = nHtml.FindByAttrContains(webSlice,'input','src',image)) {
		return imageSlice;
	}
	if (imageSlice = nHtml.FindByAttrContains(webSlice,'img','src',image)) {
		return imageSlice;
	}
	if (imageSlice = nHtml.FindByAttrContains(webSlice,'div','style',image)) {
		return imageSlice;
	}
	return false;
},

WhileSinceDidIt:function(name, seconds) {
	var now = (new Date().getTime());
	return (!GM.getLValue(name) || (parseInt(GM.getLValue(name)) < (now-1000*seconds)));
},
JustDidIt:function(name) {
	var now = (new Date().getTime());
	GM.setLValue(name, now.toString());
},
PushDidIt:function(name, seconds) {
	var v=parseInt(GM.getLValue(name,0))+(seconds*1000);
	GM.setLValue(name, v.toString());
},
// slowly, after a randomized time visit a url
VisitUrl:function(href) {
	this.waitMilliSecs=4000+Math.random()*2000;
	nHtml.VisitUrl(href);
},
NumberOnly:function(num) {
	var numOnly=parseFloat(num.replace(/[^0-9\.]/g,""));
	return numOnly;
},
RemoveHtmlJunk:function(html) {
	return html.replace(this.htmlJunkRe,'');
},

/////////////////////////////////////////////////////////////////////

//							DISPLAY FUNCTIONS

// these functions set up the control applet and allow it to be changed

/////////////////////////////////////////////////////////////////////
AddControls:function() {
	// sets up divs and input boxes for controls
	
	if(document.getElementById('AutoPlayer_controls')) {
		return true;
	}
	
	var mn=this.GetMainNav();
	if (mn) {
		var linkStyle = 'background:#fff; border: 2px solid #444; font-weight: bold; padding: 1px';

		var addLink=document.createElement('span');
		addLink.setAttribute('style',linkStyle);
		addLink.id='AutoPauseLink';
		addLink.innerHTML=(GM.getGValue('PlayerPaused','') == '') ? 'Pause' : 'Resume';
		if (GM.getGValue('WaitForCaptcha','') != '') {
			addLink.innerHTML='Captcha OK';
		}
	        addLink.addEventListener('click',function(e) {
			if (GM.getGValue('WaitForCaptcha','') != '') {
				GM.setGValue('PlayerPaused','true');
				GM.setGValue('WaitForCaptcha','');
				Player.JustDidIt("ClickCaptchaOK");
			}
			if (GM.getGValue('PlayerPaused','') == '') {
				this.innerHTML='Resume';
				GM.setGValue('PlayerPaused','true');
			} else {
				this.innerHTML='Pause';
				GM.setGValue('PlayerPaused','');
				if (GM.getGValue('DebugMode')) {
					//GM.setGValue('0.AutoFactionQuest',0);
					//GM.setGValue('0.QuestCheckList','');
				}
			}
		},false);
		mn.insertBefore(addLink,mn.childNodes[0]);

		var addLink=document.createElement('span');
		addLink.setAttribute('style',linkStyle);
		addLink.innerHTML='Settings';
	        addLink.addEventListener('click',function(e) {
			var div = document.getElementById('AutoPlayer_controls');
			if (div) {
				if (div.style.display == 'block') {
					div.style.display = 'none';
				} else {
					div.style.display = 'block';
				}
			} else {
				GM.log("No AutoPlayer_controls");
			}
		},false);
		mn.insertBefore(addLink,mn.childNodes[0]);
	} else {
		GM.log("No main nav, can't add links");
		return false;
	}

	var div=document.createElement('div');
	
	div.id='AutoPlayer_controls';	
	div.setAttribute('style','padding:5px; border:2px solid #444; background:#fff; color: #000; position: absolute; left:25%; top: 150px; display: none; z-index: 90000');	


	div.innerHTML = "\
		<style type='text/css'>label, .check1 {text-align: left; float: left; width: 10em;} .tablink {cursor: pointer; color: blue; border: 1px solid black; padding: 1px}</style>\
		<h3 style='text-align:center; padding:0; margin:0;'>Three Kingdoms Autoplayer : <span id=ac_curtabname>General</span> Options</h3>\
		<p style='text-align:left'>\
		<span class=tablink style='color:black; cursor: default;' id=ac_tab_1>General</span> \
		<span class=tablink id=ac_tab_2>Plunder</span></p>\
		<div id=ac_div_1 style='margin:0; text-align:left;'>\
		<span class=check1><input type=checkbox id='ac_1_EnableBuild'3> Build</span> \
		<input type=checkbox id='ac_1_EnableFactionQuests'> Faction Quests<br />\
		<span class=check1><input type=checkbox id='ac_1_EnableRecruit'> Recruit</span>\
		<input type=checkbox id='ac_1_EnableLootCamp'> Camp Looting<br />\
		<span class=check1><input type=checkbox id='ac_1_EnableTowerTrial'> Daily Trial</span>\
		<input type=checkbox id='ac_DebugMode'> Debug Log<br style='clear: both'/>\
		<label>Prefer Faction:</label> <input id='ac_PreferFaction'><br />\
		<label>Prefer Hero:</label> <input id='ac_PreferHero'><br />\
		<label>Prefer Recruit:</label> <input id='ac_PreferRecruit'><br />\
		</div>\
		<div id=ac_div_2 style='display:none; margin:0; text-align:left;'>\
		<label>Coordinates:</label> <textarea id='ac_PlunderCoordinates'></textarea><br />\
		<label>Troop Amount:</label> <input id='ac_PlunderTroopAmount' ac_default='100'><br />\
		<label>Attack Every:</label> <input id='ac_PlunderFrequency' ac_default='1'> Days<br />\
		</div>\
		<div id='AutoPlayer_info' style='text-align:left;'></div>\
	";
	
	document.body.appendChild(div);

	// automatic listener links input to GM.setGValue
	this.AddListeners('AutoPlayer_controls');
	return true;
},
SetDivContent:function(div, code) {
	var div = document.getElementById(div);
	if (!div) {
		GM.log("Can't find div " + div);
		return false;
	}
	div.innerHTML = code;
},
SetControls:function() {
	// sets current values for controls, runs each loop
	
	if(!this.AddControls()) return false;
	
	var htmlCode = '';
	
	htmlCode+= this.statsLine + "<br />";
	if (GM.getLValue('NeedMoreSoldiers','')!='') {
		htmlCode+="Need more soldiers for " + GM.getLValue('NeedMoreSoldiers') + "<br />";
	}
	
	htmlCode+= "Version: " + thisVersion + " ";
	if (newVersionAvailable) {
		htmlCode += "<a href='http://userscripts.org/scripts/source/" +SUC_script_num+".user.js'>(Newer version available)</a>";
	}
	htmlCode+= "<br />";

	this.SetDivContent('AutoPlayer_info', htmlCode);
},

/////////////////////////////////////////////////////////////////////

//							EVENT LISTENERS

// Watch for changes and update the controls

/////////////////////////////////////////////////////////////////////

ShowTab:function(topDivName, idNum) {
	var topDiv;
	if(!(topDiv = document.getElementById(topDivName))) {
		GM.debug("Can't find " + topDivName);
		return false;
	}
	var div = document.getElementById('ac_div_' + idNum);
	if (div) {		
		nHtml.LoopByXPath(topDiv,".//div[starts-with(@id,'ac_div_')]",function(tab) {
			tab.style.display = 'none';
		});
		nHtml.LoopByXPath(topDiv,".//span[starts-with(@id,'ac_tab_')]",function(tab) {
			tab.style.color = 'blue';
			tab.style.cursor = 'pointer';
		});
		div.style.display = 'block';
		var tab = document.getElementById('ac_tab_' + idNum);
		if (tab) {
			tab.style.color = 'black';
			tab.style.cursor = 'default';
		}
		var ctn = nHtml.FindByXPath(topDiv,".//span[@id='ac_curtabname']")
		if (ctn) {
			ctn.innerHTML = tab.innerHTML;
		}
	} else {
		GM.log("Can't find " + 'ac_div_' + idNum);
	}
},

// given a div, auto-adds listeners to all inputs that being with ac_
// if you want default_false, then do ac_f_
// inputs cannot have an "_" in the name

AddListeners:function(topDivName) {
	var topDiv;
	if(!(topDiv = document.getElementById(topDivName))) return false;
	
	nHtml.LoopByXPath(topDiv,".//input[starts-with(@id,'ac_')] | .//textarea[starts-with(@id,'ac_')]",function(inputDiv) {
		var def = inputDiv.id.match(/ac_(.*)_.*/);
		if (def) def = def[1];
		var idName = inputDiv.id.replace(/^.*_/,'')
		//GM.log(' id ' + inputDiv.id + ' def '+ def +' type ' + inputDiv.type + ' tagName ' + inputDiv.tagName);
		if (inputDiv.type=='checkbox') {
			inputDiv.addEventListener('change',function(e) {
				var idName = e.target.id.replace(/^.*_/,'')
				GM.setGValue(idName,e.target.checked);
				//GM.log("Just set " + idName);
			},false);
			inputDiv.checked = GM.getGValue(idName,def ? true : false);
		} else if (inputDiv.type=='text' || inputDiv.nodeName=='TEXTAREA') {
			inputDiv.addEventListener('change',function(e) {
				var idName = e.target.id.replace(/^.*_/,'')
				GM.setGValue(idName,e.target.value);
				//GM.log("Just set " + idName);
			},false);
			inputDiv.value = GM.getGValue(idName,'');
		}
	});
	nHtml.LoopByXPath(topDiv,".//span[starts-with(@id,'ac_')]",function(span) {
		var m = span.id.match(/ac_(.*)_([0-9]*)/);
		if (m) {
			var nam = m[1];
			var num = m[2];
			if (nam == 'tab') {
				//GM.debug("spanac" +  name + ' ' + num);
				span.addEventListener('click',function(e) {
					var idNum = e.target.id.replace(/^.*_/,'');
					Player.ShowTab(topDivName,idNum);
				},false);
			}
		}
	});
},

/////////////////////////////////////////////////////////////////////

//							GET STATS

// Functions that records all of base game stats, energy, stamina, etc.

/////////////////////////////////////////////////////////////////////

GetStats:function() {
	var rd=nHtml.FindByAttr(document.body, 'div', 'class', 'resourcedata');
	if (!rd) {
		GM.log("Resource data not found");
		var eTime = GM.getGValue("ErrorTime",0);
		if (eTime == 0)
			GM.setGValue("ErrorTime", (new Date().getTime()));
		if ((eTime > 0) && (eTime < (new Date().getTime())-(1000*60*15))) {
			GM.log('Waiting too long for stats');
			Player.ReloadNow();
		}
		return false;
	}
	GM.setGValue("ErrorTime",0);

	var ci=nHtml.FindByAttr(document.body, 'div', 'class', 'city');
	
	this.stats = {};
	
	this.statsLine = '';
	var list = ['lumber', 'clay', 'crop', 'iron'];
	for (var i in list) {
		var r = list[i];

		this.stats[r] = {};
		if (ci) {
			var el = nHtml.FindByAttrContains(ci, "font", "id", r);
			if (el)
				this.stats[r].inc = parseInt(nHtml.GetText(el).trim());
			else 
				GM.log("Font id " + r + " not found in city (inc)");
		}
		
		var el = nHtml.FindByAttrContains(rd, "span", "id", r + '_now');
		if (el)
			this.stats[r].cur = parseInt(nHtml.GetText(el).trim());
		else 
			GM.log("Span id " + r + "_now not found");

		// GM.debug("El found + " + l + " " + nHtml.GetText(el).trim());
		
		var el = nHtml.FindByAttrContains(rd, "span", "id", r + '_max');
		if (el)
			this.stats[r].max = parseInt(nHtml.GetText(el).trim());
		else
			GM.log("Span id " + r + "_max not found");
			
		var l = r;
		if (r=='crop') l = 'food';
		if (r=='lumber') l = 'wood';
		if (r=='clay') l = 'stone';
		this.stats[l] = this.stats[r];
		
		this.statsLine = this.statsLine + l.substr(0,1) + ': ' + this.stats[r].cur + '/' + this.stats[r].max + '+' + this.stats[r].inc + ' ';
	}
	//GM.log(this.statsLine);
	
	return this.stats.lumber.cur > 0 || this.stats.clay.cur > 0 || this.stats.iron.cur > 0;
},

waitMilliSecs:5000,

/////////////////////////////////////////////////////////////////////

//							MAIN LOOP

// This function repeats continously.  In principle, functions should only make one
// click before returning back here.

/////////////////////////////////////////////////////////////////////


MainLoop:function() {
	var ok = this.GetStats();
	this.SetControls();

	if (GM.getGValue('PlayerPaused','') != '') {
		this.WaitMainLoop();
		return;
	}

	if (!ok) {
		GM.log("Wait for stats.");
		GM.debug("Debug mode is ON");
		this.WaitMainLoop();
		return;
	}


	if (GM.getGValue("WaitForCaptcha",'') == '') {
		var img = nHtml.FindByAttr(document.body, "img", "id", 'validate_img');
		if (img && nHtml.IsVisible(img)) {
			// todo ... try to ocr it?
			if (this.WhileSinceDidIt("ClickCaptchaOK",60*5)) {
				var el;
				if (el = document.getElementById('AutoPauseLink')) {
					GM.setGValue("WaitForCaptcha", true);
					el.innerHTML = 'Captcha OK';
				}
			}
			GM.log("Wait for captcha");
			this.WaitMainLoop();
			return;
		}
	}
	
	var citylist = new Array;
	var nCit = 0;
	var selCity = 0;
	nHtml.LoopByXPath(document.body,".//ul[@id='listallcity']//a",function(el){
		//GM.log("classname is " + el.className + " nCit is " + nCit);
		citylist.push(el);
		if (el.className.match(/now/i)) {
			//GM.log("match nCit is " + nCit);
			selCity=nCit;
		}
		++nCit;
	});
	
	//GM.log("Selcity is " + selCity);
	
	// change GM prefix for city selection
	GM.lprefix = tk_server + '.' + selCity + '.';
	
	// log & close system notices
	var pop = nHtml.FindByAttr(document.body, "div", "class", 'dialog_header');
	if (nHtml.IsVisible(pop)) {
		var title = nHtml.FindByXPath(pop, ".//h3");
		var msg = nHtml.FindByXPath(document.body, ".//div[@id='dialog']//h4");
		if (title) title=nHtml.GetText(title).trim();
		if (msg) msg=nHtml.GetText(msg).trim().toLowerCase();
		if (title && title.match(/(system notice)|(special event)/i) && !msg.match(/this will cost you.*gold/i)) {
			var bclose = nHtml.FindByXPath(document.body, ".//div[@id='dialog']//div[@class='dialogbtn']//a[@class='submitbtn']");
			if (!bclose) {
				GM.debug("No submit button");
				bclose = nHtml.FindByAttr(pop, "a", "class", "closewindow");
			}
			if (bclose) {
				GM.log("Close pop " + title + " : " + msg);
				GM.setLValue("PopWasClosed", title + " : " + msg);
				this.Click(bclose);
				this.WaitMainLoop();
				return;
			}
		}
	}

	//GM.log("Main loop");
	
	var did = false;
	if (!did) did = this.MainHeroStats();
	if (nCit <= 1 || selCity>=(nCit-1)) {
		// do these in most recent for most benefit
		if (!did) did = this.MainDailyStipend();
		if (!did) did = this.MainDailyHero();
	}
	if (selCity == 0) {
		if (GM.getGValue("WaitForCaptcha",'') == '') {
			// only try faction quests with main city
			if (!did) did = this.MainFaction();
		}
	}
	//if (!did) did = this.MainPlunder();
	if (GM.getGValue("WaitForCaptcha",'') == '') {
		if (!did) did = this.MainCamp();
	}
	if (!did) did = this.MainTrial();
	if (!did) did = this.MainBuild();
	if (!did) did = this.MainRecruit();

	if (!did) 
		did = this.CloseFloat();

	// change GM prefix to server-wide
	GM.prefix = tk_server + '.';
	
	if (!did) {
		if (nCit > 1) {
			if (this.WhileSinceDidIt("CitySwitch",60)) {
				selCity=selCity+1;
				if (selCity >= nCit) 
					selCity=0;
				GM.debug("City switch to " + selCity + " " + nHtml.GetText(citylist[selCity]).trim());
				this.Click(citylist[selCity]);
				did = true;
				this.JustDidIt("CitySwitch");
			}
		}
	}
	
	this.WaitMainLoop(); 
	return;
},
CloseFloat:function () {
	// close all pops
	var ext = arguments.callee ? (" called from " + arguments.callee.caller.name) : '';
	var clicked = false;
	
	nHtml.LoopByXPath(document.body, ".//div[@id='dialog']//a[@class='closewindow']", function (bclose) {
		if (nHtml.IsVisible(bclose)) {
			if (bclose.parentNode)
				GM.debug("Closing dialog " + nHtml.GetText(bclose.parentNode).trim() + ext);
			nHtml.Click(bclose);
			clicked = true;
			return true;
		}
	});
	if (clicked) return true;
	
	nHtml.LoopByXPath(document.body, ".//div[contains(@id,'floatblock')]//a[@class='closewindow']", function (bclose) {
		if (nHtml.IsVisible(bclose)) {
			if (bclose.parentNode)
				GM.debug("Closing floatblock " + nHtml.GetText(bclose.parentNode).trim() + ext);
			nHtml.Click(bclose);
			clicked = true;
			return true;
		}
	});
	return clicked;
},
DailyQuestNav:function(qname) {
        if (this.Goto('quest')) return true;
	if (!this.navOK) return false;

	this.navOK = false;

	qname=qname.toLowerCase();
	
	var sw=nHtml.FindByAttr(document.body, "div", "id", 'floatblockcenter');
	var db=nHtml.FindByXPath(sw, ".//ul[@class='subnav']//li//a[contains(text(),'Daily Quest')]");

	if (!db) {
		GM.log("Can't find Daily Quest");
		return false;
	}
	
	if (!nHtml.IsVisible(db) || db.parentNode.className != 'now') {
		GM.debug("Click daily quest " + qname);
		this.Click(db);
		return true;
	}
		
	var tc=nHtml.FindByXPath(sw, ".//div[@id='taskcontent']");
	if (!tc) {
		GM.log("Can't find task content");
		return false;
	}
	
	var name = nHtml.FindByAttrContains(tc, "span", "class", "redtext");
	if (name) name=nHtml.GetText(name).trim();

	if (name.toLowerCase().indexOf(qname) < 0) {
		var ul=nHtml.FindByAttrContains(document.body, "ul","id","taskTypeListAll");
		if (!ul) {
			GM.log("Can't find ul task list");
			return false;
		}
		var qlink=nHtml.FindByXPath(ul, ".//font[contains(translate(text(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),'" + qname + "')]");
		if (!qlink) {
			GM.log("Can't find link for " + qname);
			return false;
		}
		Player.Click(qlink);
		return true;
	}

	GM.log("Quest name " + name + " matches " + qname);
	
	var to=nHtml.FindByAttrContains(tc, "div","class","taskoperate");
	if (!to) GM.log("Can't find task operate for " + name);
	if (to.tagName != 'A') {
		to=nHtml.FindByTag(to,'A');
		if (!to) GM.log("Can't find task operate click A for " + name);
	}
	
	this.questOperate = to;
	this.questName = name;
	this.questWindow = sw;	
	this.navOK = true;

	return false;
},
MainDailyStipend:function() {
	// once per day
	if (!this.WhileSinceDidIt("AutoDailyQuest",60*60*12)) return false;
	if (this.DailyQuestNav('stipend')) return true;
	if (!this.navOK) return false;
	this.JustDidIt("AutoDailyQuest");
	if (this.questOperate) {
		this.Click(this.questOperate);
		return true;
	}
	return false;
},
MainDailyHero:function() {
	// once per hour
	if (!this.WhileSinceDidIt("AutoHeroQuest",60*60+Math.random()*60*5)) 
		return false;
	
	if (GM.getLValue("CurrentHermitHero","") == "") {
		if (this.PickArea('city', "governor")) return true;
		if (!this.navOK) return false;

		if (!this.currentPop) {
			GM.log("No pop after pickarea!");
			return false;
		}
		
		var ch = nHtml.FindByAttrContains(this.currentPop,'a', 'onclick', 'urrent hero');
		
		if (!ch) {
			GM.log("No current hero");
			return false;
		}

		ch = String(ch.getAttribute('onclick'));
		GM.log(ch);
		
		ch = ch.match(/is:([^&<]+)[&<]/);
		if (!ch) {
			GM.log("No current hero match");
			return false;
		}

		ch = ch[1];
		ch.trim();
		GM.debug("Current hero is " + ch);
		GM.setLValue("CurrentHermitHero",ch);

		return true;
	}
	
	if (this.DailyQuestNav('book of hero')) return true;
	if (!this.navOK) return false;
	
	this.JustDidIt("AutoHeroQuest");
	
	gn = nHtml.FindByAttr(this.questWindow,"input","id","famous_gname");
	if (!gn) {
		GM.setLValue("CurrentHermitHero","");
		GM.log("No hermit input, too soon?");
		return false;
	}
	
	gn.value=GM.getLValue("CurrentHermitHero");
	this.Click(this.questOperate);
	GM.setLValue("CurrentHermitHero","");
	
	return true;		
},
MainHeroStats:function() {
	this.heroStats = {};
	
	var heroStr = GM.getLValue("HeroStats", '');
		
	//GM.log("str is " + heroStr);
	var sp = heroStr.split(';');
	for (var i in sp) {
		var ent=sp[i]; 
		if (ent != '') {
			var p = ent.indexOf(':');
			var name = ent.substr(0,p);
			var dat = ent.substr(p+1);
			this.heroStats[name] = {};
			var dsp=dat.split(','); 
			for (var j in dsp) {
				var vent=dsp[j];
				if (vent != '') {
					var p2 = vent.indexOf('=');
					var k = vent.substr(0,p2);
					var v = vent.substr(p2+1);
					//GM.log("k=v is " + k + '=' + v);
					this.heroStats[name][k]=v;
				}
			}
		}
	}
	
	if (this.WhileSinceDidIt("AutoHeroLevel",60*60*2)) {
		GM.setLValue("NeedHeroLevel", true);
		this.JustDidIt("AutoHeroLevel");
	}

	if (!this.WhileSinceDidIt("AutoHeroStats",60*15)) 
		return false;

	var needLevel = GM.getLValue('NeedHeroLevel', false);

	if (needLevel) {
		//GM.debug("Need level");
		var hd = nHtml.FindByAttrContains(document.body, 'a','onclick','general.uplevel');
		if (GM.getLValue('PopWasClosed','').match(/enough experience/i)) {
			GM.setLValue('PopWasClosed','');
			return this.CloseFloat();
		}
		if (GM.getLValue('PopWasClosed','').match(/residing in the/i)) {
			GM.setLValue('PopWasClosed','');
			return this.CloseFloat();
		}
		if (hd && nHtml.IsVisible(hd)) {
			GM.debug("Need level, so click up");
			this.Click(hd);
			return true;
		}
	}
	
	pop = nHtml.FindByXPath(document.body, ".//div[@id='floatblockleft']");
	var title = nHtml.FindByXPath(pop, ".//h4 | .//h2");			
	if (title) 
		title=nHtml.GetText(title).trim().toLowerCase();
	
	if (title && title.match(/governor/i)) {
		//GM.log("title is " + title);

		var tab = nHtml.FindByXPath(pop, ".//div[@id='school_2']//table");

		if (!tab) {
			GM.log("Can't find hero tab");
			return false;
		}
		
		Player.heroStats = {};
		var doUp = false;
		var upList = GM.getLValue("UpgradeList",'');
		GM.debug("Uplist " + upList);
		nHtml.LoopByXPath(tab, ".//tr", function(el) {
			var cols = [];
			nHtml.LoopByXPath(el, ".//th | .//td", function(col) {
				cols.push(nHtml.GetText(col).trim().toLowerCase());
			});
			
			var meet = nHtml.FindByXPath(el, ".//a[contains(@onclick,'do=meet')]");
			
			if (meet) {
				doUp=true;
				Player.Click(meet);
				return 'break';
			} else {
				//GM.debug("No meet?");
			}
			
			if (cols[0] == 'hero name') return;
			if (cols[0] == '') return;
				
			Player.heroStats[cols[0]] = {};	
			Player.heroStats[cols[0]].name = cols[0];
			Player.heroStats[cols[0]].position = cols[1];
			Player.heroStats[cols[0]].dignity = cols[2];
			Player.heroStats[cols[0]].stamina = cols[3];
			Player.heroStats[cols[0]].level = cols[4];
			Player.heroStats[cols[0]].time = (new Date().getTime());

			if (needLevel && upList.indexOf(cols[0]) < 0) {
				var hd = nHtml.FindByAttrContains(el, 'strong','onclick','general.detail');
				GM.setLValue("UpgradeList",upList + ',' + cols[0])
				if (hd) {
					doUp=true;
					Player.Click(hd);
					return 'break';
				}
			}
		});
		if (doUp)
			return true;
			
		if (needLevel) {
			GM.debug("Done upgrading");
			GM.setLValue("NeedHeroLevel",'');
			GM.setLValue("UpgradeList",'');
		}

		var heroStr = '';
		for (hero in this.heroStats) {
			if (hero == '') continue;

			heroStr = heroStr + hero + ":";
			for (var x in this.heroStats[hero]) {
				heroStr = heroStr + x + "=";
				heroStr = heroStr + this.heroStats[hero][x] + ",";
			}
			heroStr = heroStr + ";";
		}
		
		if (heroStr != '') {
			GM.log("Hero stats: " + heroStr);
			GM.setLValue("HeroStats", heroStr);
			this.JustDidIt("AutoHeroStats");
			return false;
		}
	}
	
	var clk=this.HeroClick();
	if (clk) {
		GM.debug("Click general for hero stats");
		this.Click(clk);
		return true;
	}
	return false;
},
HeroClick:function() {
	var as=nHtml.FindByAttr(document.body, 'div', 'class', 'armystatistics');
	if (!as) {
		GM.log("No armystatistics, probably an error");
		return null;
	}
	var img=nHtml.FindByAttrContains(as, 'img', 'src', 'icon/general.gif');
	if (!img) {
		//GM.debug("No icon/general found, heroes busy?");
	}
	var clk=nHtml.FindByAttrContains(as, 'a', 'onclick', 'general_list');
	if (!clk) {
		//GM.debug("No general_click, heroes busy?");
		return null;
	}
	return clk;
},
MainCamp:function() {
	if (!GM.getGValue('EnableLootCamp',true)) 
		return false;

	var fmb = nHtml.FindByXPath(document.body,".//div[@id='floatblockleft']");
	var fmh = nHtml.FindByTag(fmb,"h2");
	var popName = '';
	if (fmh)
		popName = nHtml.GetText(fmh).trim();
	if (fmb && !fmh) {
		//GM.log("No header in fmb, usually OK");
	}

	if (!this.WhileSinceDidIt("AutoLootCamp",600)) return false; 
	if (!this.HeroClick()) {
		GM.debug("No heroes for camp run"); 
		this.JustDidIt("AutoLootCamp");
		return false;
	}

	var hero = nHtml.FindByAttr(document.body, "select", "id", "self_gid");	
	var selectedHero = '';
	if (hero) {
		if (!this.heroStats)
			GM.log("ERROR: why no stats?");
		
		if (hero.options.length <= 1) {
			GM.log("All heroes are busy, no camp attacks");
			this.JustDidIt("AutoLootCamp");
			return this.CloseFloat();
		}
		
		var pref = GM.getGValue("PreferHero",'').trim().toLowerCase();
		for (var i in hero.options) {
			if (i == 0) continue;
			
			var name = hero.options[i].text.trim().toLowerCase();
			//GM.log("NAME " + name);
			
			if (this.heroStats && this.heroStats[name] && this.heroStats[name].stamina > 20) {
				hero.options[i].selected = true;
				if (pref != '' && (name.indexOf(pref) >= 0 || pref.indexOf(name) >= 0))
					break;
				else if (pref == '')
					break;
			}
			
			if (this.heroStats && !this.heroStats[name])
				GM.log("ERROR: why not stats for " + name);
		}

		selectedHero = hero.options[hero.selectedIndex].text.trim().toLowerCase();
		if (this.heroStats && this.heroStats[selectedHero]) {
			if (this.heroStats[selectedHero].stamina < 20) {
				GM.log("Can't loot, stamina for " + selectedHero + " is " + this.heroStats[selectedHero].stamina);
				this.JustDidIt("AutoLootCamp");
				return this.CloseFloat();
			}
		} else {
			GM.log("No stats! for " + selectedHero);
			GM.setLValue("AutoHeroStats",0);
		}
	}

	if (popName && (popName == GM.getLValue("CampFacLast",''))) {
		var lev_max;
		var title_max;
		var el_max;		
		nHtml.LoopByXPath(fmb,".//map/area",function(el) {
			var title=el.getAttribute('sgtitle');
			if (!title)
				return;
				
			var level = title.match(/Level(\d+)/i);
			if (!level) {
				//GM.log("No level for area " + title);
				return;
			}
			
			level=level[1];
			if (this.heroStats && (level*.97) > this.heroStats[selectedHero].level)
				return;

			if (level < lev_max)
				return;

			title_max=title;
			lev_max=level;
			el_max=el;
		});

		this.JustDidIt("AutoLootCamp");
		
		if (!el_max) {
			GM.log("Can't find general to attack, maybe need to upgrade heroes");
			GM.setLValue('NeedHeroLevel', true);
			return this.CloseFloat();
		}

		GM.log("Click on general " + popName + " " + title_max);
		this.Click(el_max);
		return true;
	}

	if (popName == 'Faction Map') {
		var cit = [];
		GM.log("Found faction map header");
		nHtml.LoopByXPath(fmb,".//span[starts-with(@id,'influence_')]/a",function(el) {
			var facName=nHtml.GetText(el).trim();
			if (GM.getLValue("CampFacLast",'') != el.facName)
				cit.push(el);
		});
		
		if (cit.length == 0) {
			GM.log("No faction cities?");
			return false;
		}
		
		rcit = (Math.random()*cit.length);
		//GM.log("rcit1 is " + rcit);
		rcit = 0|rcit;
		//GM.log("rcit2 is " + rcit);
		var el = cit[rcit];
		
		var facName=nHtml.GetText(el).trim();
		GM.setLValue("CampFacLast", facName);
		this.Click(el);
		
		return true;
	}
	
	if (popName) {
		GM.log("Loot pop name is " + popName);
	}

	var fm = nHtml.FindByXPath(document.body,".//div[@id='floatbutton']//img[contains(@sgtitle,'Faction Map')]");
	if (!fm) {
		GM.log("Can't find faction map");
		this.JustDidIt("AutoLootCamp");
		return this.CloseFloat();
	}
	
	GM.debug("Click on faction map");
	this.Click(fm);
	return true;
}
,
MainPlunder:function() {
	if (GM.getGValue('PlunderCoordinates','').trim()=='') 
		return false;

	if (!this.WhileSinceDidIt("AutoPlunder",60*10)) 
		return false;
		
	if (!this.HeroClick()) {
		return false;
	}
}
,
MainTrial:function() {
	if (!GM.getGValue('EnableTowerTrial',true)) 
		return false;

	if (!this.WhileSinceDidIt("AutoTowerTrial",60*60*23.5)) 
		return false;
		
	if (!this.HeroClick()) {
		return false;
	}

	var pop = nHtml.FindByXPath(document.body, ".//div[@id='dialog']");
	if (pop && nHtml.IsVisible(pop)) {
		var title = nHtml.FindByXPath(pop, ".//h4");			
		if (title) {
			title=nHtml.GetText(title).trim().toLowerCase();
			this.currentPopName=title;
			GM.debug("Current tt pop is " + title);
		}
		if (title.match(/this will cost you.*gold/)) {
			var usefree = nHtml.FindByXPath(pop, ".//input[@id='useFreeImproveExp']");
			if (usefree) {
				usefree.checked=true;
				var bsub = nHtml.FindByXPath(document.body, ".//div[@id='dialog']//div[@class='dialogbtn']//a[@class='submitbtn']");
				if (!bsub) {
					GM.log("No submit button on dialog?");
					this.PushDidIt("AutoTowerTrial",60*60);
					return false;
				}
				if (bsub) {
					GM.log("Using free tower trial");
					this.JustDidIt("AutoTowerTrial");
					this.Click(bsub);
					return true;
				}
			} else {
				GM.debug("No free chance, wait an hour");
				this.PushDidIt("AutoTowerTrial",60*60);
				return false;
			}
		}
		if (title == 'tower of trials') {
			var hero = nHtml.FindByAttr(document.body, "select", "id", "general_1");
			if (hero.options.length > 1) {
				var pref = GM.getGValue("PreferHero",'').trim().toLowerCase();
				hero.options[1].selected = true;
				if (pref != '') {
					for (var i in hero.options) {
						if (hero.options[i].text.trim() != '') {
						if (hero.options[i].text.toLowerCase().indexOf(pref) >= 0 || pref.indexOf(hero.options[i].text.toLowerCase())>=0) {
							hero.options[i].selected = true;
							GM.log("Using hero " + i + ' ' + hero.options[i].text);
							break;
						}
						}
					}
				}
				var wb = nHtml.FindByAttr(pop, "a", "class", "submitbtn");
				if (!wb) {
					GM.log("Can't find ok button for Tower Trial");
					this.JustDidIt("AutoTowerTrial");
					return this.CloseFloat();
				}
				this.Click(wb);
				return true;
			} else {
				GM.log("No heroes left for Tower Trial");
				this.JustDidIt("AutoTowerTrial");
				return this.CloseFloat();
			}
		}
	}
	
	var tt = nHtml.FindByXPath(document.body,".//div[@id='floatbutton']//img[contains(translate(@sgtitle,'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),'tower of trials')]");
	if (!tt) {
		GM.log("Can't find Tower Of Trials");
		//this.JustDidIt("AutoTowerTrial");
		return false;
	}
	
	GM.debug("Click on tower of trials");
	this.Click(tt);
	return true;
}
,
MainFaction:function() {
	if (!GM.getGValue('EnableFactionQuests',true)) 
		return false;

	if (!this.WhileSinceDidIt("AutoFactionQuest",600)) return false;
	if (!this.HeroClick()) {
		GM.debug("No heroes for faction quest"); 
		this.JustDidIt("AutoFactionQuest");
		return false;
	}

	if (GM.getLValue('PopWasClosed','').match(/level is too low/i)) {
		GM.debug("Level too low");
		GM.setLValue('QuestFollowName', '');
		GM.setLValue('PopWasClosed','');
		return this.CloseFloat();
	}

	// the name of the quest i followed
	var questFollowName=GM.getLValue('QuestFollowName');
	var questActionName=GM.getLValue("QuestActionName");

	// solf
	var solf = nHtml.FindByXPath(document.body, ".//form[@id='soldierf']//a[contains(@href,'village.detail')]");
	var solfName;
	
	if (solf) solfName = nHtml.GetText(solf).trim().toLowerCase();

	if (solf && solfName && solfName.indexOf(questFollowName)>=0) {
		var s2 = nHtml.FindByXPath(document.body, ".//div[@id='school_2']");
		var wb = nHtml.FindByAttr(s2, "a", "class", "submitbtn");
		if (!wb) {
			GM.log("Solf fail " + solfName);
			return false;
		}
		GM.log("Solf war start " + solfName);
		this.Click(wb);
		return true;
	}

	if (solf) {
		GM.log("Not my solf " + solfName);
	}
	
	// the thing i'm aiming for (battle wise)
	var aim = nHtml.FindByXPath(document.body, ".//input[@id='aim_name']");
	var aimName;
	if (aim) 
		aimName = String(aim.value).trim().toLowerCase();	

	if (aim && GM.getLValue('PopWasClosed','').match(/too few soldiers/i)) {
		GM.setLValue('PopWasClosed','');
		GM.setLValue("NeedMoreSoldiers", questActionName);
		aimName = '';	// skip next section
		GM.log("Need more soldiers for " + questActionName);
	}
	
        if (aimName && (aimName == questFollowName)) {
		//GM.log("solf is " + aim.form.id);
		var s = new Array;
		var sHv = new Array;
		for (i = 0; i <= 9; ++i) {
			s[i] = nHtml.FindByAttr(aim.form, "input", "id", "soldier"+i);
			var sH = nHtml.FindByAttr(aim.form, "a", "id", "soldier_link" + i);
			sHv[i] = 0;
			if (sH) 
				sHv[i] = parseInt(nHtml.GetText(sH).trim().replace(/[()]/,'')); 
		}
				
		GM.debug("Militia/swordsman is " + sHv[0] + " Scout is " + sHv[9]);
		
		// for scouting, use s9 ... the scouts only
		// for other, use anyone, in order
		
		var isScout = questActionName.match(/scout/);
		
		var nS = nHtml.FindByAttr(aim.form, "input", "id", "uit_condition_soldier_num");
		if (nS) 
			nS = parseInt(nS.value);
		
		var starti = isScout?9:0;
		
		GM.debug("nS is " + nS + " s[si]: " + s[starti] + " sHv[si]: " + sHv[starti]);
		if (s[starti] && (sHv[starti]>10)) {
			var tot=0;
			if (nS > 0) {
				// know how many? send that
				for (var i=starti;i<=9;++i) {
					if (tot>=nS) 
						break;

					if (sHv[i] > (nS-tot))
						s[i].value = (nS-tot);
					else if (sHv[i] > 0)
						s[i].value = sHv[i];

					tot+=parseInt(s[i].value);
				}
			} else {
				// else, just be lazy and send everyone
				for (var i=starti;i<=9;++i) {
					s[i].value = sHv[i];
					tot+=parseInt(s[i].value);
				}
				// but log it
				GM.log("Don't know how many to send, fix code!");
			}			
			GM.log("Aim war start " + aimName + " with " + tot);
			var wb = nHtml.FindByXPath(document.body, ".//a[contains(@onclick,'warStart')]");
			this.Click(wb);
			return true;
		} else {
			if (!s[starti]) {
				GM.log("Error: no s[" + starti + "] on aim Form.");
				return false;
			} else {
				GM.log("Not enough troops to " + questActionName + " " + aimName);
				if (sHv[starti]>10) 
					GM.setLValue('NeedMoreSoldiers',questActionName);
			}
		}
	} else {
		if (aimName) {
			GM.log("Aiming for " + aimName + " but doesn't match " + questFollowName);
		}
	}

	// is there a quest action?
	var action = nHtml.FindByXPath(document.body, ".//div[@class='junying_right']//a[contains(@onclick,'startITask')]");
	if (action) {
		var hero = nHtml.FindByAttr(document.body, "select", "id", "self_gid");
		if (hero.options.length > 1) {
			var pref = GM.getGValue("PreferHero",'').trim().toLowerCase();
			if (pref != '') {
				for (var i in hero.options) {
					if (hero.options[i].text.toLowerCase().indexOf(pref) >= 0 || pref.indexOf(hero.options[i].text.toLowerCase())>=0) {
						hero.options[i].selected = true;
						GM.log("Using hero " + hero.options[i].text);
						break;
					}
				}
			}
			var aName = nHtml.GetText(action).trim().toLowerCase();
			GM.log("Click action " + questFollowName + " : " + aName);
			GM.setLValue("QuestActionName", aName);
			this.Click(action);
			return true;
		} else {
			GM.log("No heroes left for quests");
			this.JustDidIt("AutoFactionQuest");
			return this.CloseFloat();
		}
	}

        if (this.Goto('quest')) return true;
        if (!this.navOK) return false;

	var sw=nHtml.FindByAttr(document.body, "div", "id", 'floatblockcenter');
	var ul=nHtml.FindByAttrContains(sw, "ul", "class", "subnav");
	var fb=nHtml.FindByAttrContains(ul, "a", "onclick", "InfluenceTask");

        if (!fb) {
		GM.log("No faction click?");
		return false;
	}
	
        if (fb.parentNode.className != 'now') {
		GM.log("Faction click");
		this.Click(fb);
		return true;
	}
	
	var ul=nHtml.FindByAttrContains(sw, "ul","id","taskTypeListAll");
	var fac;
	var facName;
	var questCheckList = GM.getLValue('QuestCheckList','');
	
	nHtml.LoopByXPath(ul, ".//li/a",function(el){
		var n=nHtml.GetText(el);
		n = n.trim().toLowerCase();
		if (questCheckList.indexOf(n) < 0) {
			if (!fac || (nHtml.InCList(GM.getGValue('PreferFaction',''),n) >= 0)) {
				fac = el;
				facName = n;
			}
		}
	});
	
	if (!fac) {
		// all followed
		GM.log("No facs left");
		this.JustDidIt("AutoFactionQuest");
		GM.setLValue('QuestCheckList', '');
		return this.CloseFloat();
	}
	
	if (fac.parentNode.className != 'now') {
		GM.debug("Selecting " + facName);
		this.Click(fac);
		return true;
	}
	
	GM.debug("Selected " + facName);

	var questSubCheckList = GM.getLValue('QuestSubCheckList','');

	var sub;
	var subName;	
	var ul=nHtml.FindByAttrContains(sw, "ul","id","taskListAll");
	nHtml.LoopByXPath(ul, ".//li/a",function(el){
		var n=nHtml.GetText(el);
		n = n.trim().toLowerCase();
		if (questSubCheckList.indexOf(n) < 0) {
				sub = el;
				subName = n;
				return 'break';
		}
	});

	if (!sub) {
		GM.setLValue('QuestCheckList', questCheckList + ',' + facName);
		GM.setLValue('QuestSubCheckList', '');
		return true;
	}
	
	if (sub.parentNode.className != 'now') {
		GM.log("Selecting sub " + subName);
		this.Click(sub);
		return true;
	}

	GM.log("Selected sub " + subName);

	GM.setLValue('QuestSubCheckList', questSubCheckList + ',' + subName);

	var notstart = nHtml.FindByXPath(sw, ".//li[contains(text(),'not started')]");
	if (notstart) {
		var action = nHtml.FindByXPath(sw, ".//ul[@class='taskcontent']//strong[@class='tips_green']/parent::a");	
		if (!action) {
			GM.log("Can't find tips_green under " + facName + " quest");
			return false;
		}
		var aName = nHtml.GetText(action).trim().toLowerCase();
		GM.setLValue('QuestFollowName', aName);
		GM.log("Following quest for " + aName);	
		this.Click(action);
		return true;
	} else {
		GM.debug("Already done " + facName + ':' + subName);
	}
	return true;
},
MainRecruit:function() {
	if (!GM.getGValue('EnableRecruit',true)) 
		return false;

	if (GM.getLValue('NeedMoreSoldiers','')=='')
		return false;
	
	if (!this.WhileSinceDidIt('Recruit_'+name,60*15))
		return false;

	var pref = GM.getGValue('PreferRecruit', '').toLowerCase().trim();

	if (GM.getLValue('NeedMoreSoldiers','').indexOf('scout')>=0) {
		pref='scout';
	}

	if (this.needFood)
		return false;
	
	if (pref.indexOf('scout')>=0 || pref.indexOf('crossbowman')>=0) {
		if (this.PickArea('city', 'scout camp'))
			return true;		
		if (!this.navOK) {
			GM.debug("No scout camp");
			Player.JustDidIt('Recruit_'+name);
			return false;
		}
	} else if (pref.indexOf('lance')>=0) {
		if (this.PickArea('city', 'stable'))
			return true;		
		if (!this.navOK) {
			GM.debug("No stable");
			Player.JustDidIt('Recruit_'+name);
			return false;
		}
	} else {
		if (this.PickArea('city', 'barracks'))
			return true;		

		if (!this.navOK) {
			GM.debug("No barracks");
			Player.JustDidIt('Recruit_'+name);
			return false;
		}
	}
	
	GM.debug("Recruiting " + pref);
	
	var sw = nHtml.FindByAttr(this.currentPop, 'div', 'class', 'subwindow_right');
	var con = nHtml.FindByAttr(sw, 'div', 'class', 'construct');
	var okdid = false;
	nHtml.LoopByXPath(sw, ".//div[@class='construct']//ul/li",function(li){
		var info = nHtml.FindByXPath(li, ".//div[@class='infoshow']");
		if (info) {
			var name=nHtml.GetText(info);
			name=name.replace(/\s+now:.*/i,'').trim().toLowerCase();
			name=name.replace(/\s+requirement:.*/i,'');
			if ((pref=='') || name.indexOf(pref) >= 0) {
				//GM.debug("Recruit name is " + name + " pref is " + pref);
				var avail=nHtml.GetText(info);
				avail=avail.replace(/.*available to recruit\s*[:()\s]*/i,'');
				//GM.debug("avail is " + avail);
				avail=parseInt(avail);
				var but = nHtml.FindByXPath(li, ".//a[contains(@onlick,'raiseSoldierStart')]");
				if (!but)
					but = nHtml.FindByXPath(li, ".//a[@sgtitle='Recruit']");
				var inp = nHtml.FindByXPath(li, ".//input[@type='text']");
				// GM.log("Name: " + name + " Avail: " + avail + " But: " + but + " In: " + inp);
				if (inp && avail>1 && but) {
					GM.log("Recruit " + 0|(avail/2) + " " + name);
					inp.value=0|(avail/2);
					Player.Click(but);
					GM.setLValue('NeedMoreSoldiers','');
					okdid = true;
				} else {
					GM.log("Can't recruit " + name + " avail " + avail);
				}
				// for pref = '' ... keep looping for cheapest?  or recrut militia/swordsman?
				return 'break';
			}
		}
	});

	this.JustDidIt('Recruit_'+name);
	if (!okdid) 
		GM.log("Can't recruit " + name);
	return okdid;
},
MinCityLev:function(list, prior) {
	var minL = 10000;
	var minN = '';
	if (prior) {
		var lev = this.cityStats[prior];
		if (lev>0) 
			minL = lev-1;
	}
	for (var i in list) {
		var name = list[i];
		var lev = this.cityStats[name];
		if (name == 'tower' || name == 'city wall') {
			//GM.debug(name + " LEVEL " + lev);
		}
		if (lev != undefined) {
			//GM.debug(name + " LEVEL DEF " + lev);
			if (lev < minL) {
				minN = name;
				minL = lev;
			}
		}
	}
	return minN;
},
MainBuild:function() {
	if (!GM.getGValue('EnableBuild',true)) 
		return false;

	// get current building list
	var iq=nHtml.FindByAttr(document.body, "div", "id", 'indexqueue');
	var ci=iq ? nHtml.FindByAttr(iq, "div", "class", 'constructinfo') : iq;
	if (ci) {
		var blist = '';
		if (ci) {
			nHtml.LoopByXPath(ci, ".//li", function (li) {
				var b = nHtml.GetText(li).trim();
				var t = b.match(/(.*)\t/);
				if (t) b = t[1].trim();
				if (b.match(/Lv\.\d/i)) {
					blist = blist + "," + b;
				}
			});
		}
		//GM.log("blist: " + blist);
		this.buildingList = blist.substr(1);

		// if in city and no building list... do it now
		if (this.buildingList == '')
			GM.setLValue('AutoCheckBuild',0);
	} else {
		GM.debug("Can't find constructinfo!");
	}

	//GM.debug("Build list is " + this.buildingList);

	if (!this.WhileSinceDidIt('AutoCheckBuild',60))
		return false;

	if (!ci || this.buildingList != '') {
		this.JustDidIt("AutoCheckBuild");
		return false;
	}	
	
	this.needFood = this.stats.crop.inc < this.stats.crop.max*.005;
	
	var did = false;
	
	//GM.debug("1. Need food " + this.needFood + " did " + did);	
	if (this.WhileSinceDidIt('AutoResource',300)) {
		if (this.needFood) {
			GM.log('Food is low try build farmland');
			did = this.Build('resource', 'farmland');
		} else {
			if (!did && this.stats.clay.inc < (this.stats.lumber.inc*.90) && this.stats.clay.inc < this.stats.iron.inc)
				did = this.Build('resource', 'stone mine');

			if (!did && this.stats.iron.inc < (this.stats.lumber.inc*.90))
				did = this.Build('resource', 'iron mine');

			if (!did)
				did = this.Build('resource', 'timberland');			
		}
		if (!did) 
			this.JustDidIt('AutoResource');
	}

	//GM.debug("2. Need food " + this.needFood + " did " + did);	
	if (!did && this.WhileSinceDidIt('AutoCity',600)) {
		if ( !this.needFood ) {
			GM.debug("3. Need food " + this.needFood + " did " + did);	

			// priority mansion or annex, but don't create them
			if (!did) did = this.Build('city', 'mansion', 'nocreate');
			if (!did) did = this.Build('city', 'annex', 'nocreate');

			if (!did) {
				// nav to city to be sure citystats is set
				if (this.PickArea('city')) return true;
				if (!this.navOK) return false;

				GM.log("CS: " + this.cityStats);

				// ok to create these				
				var minC = this.MinCityLev(['barracks', 'weaponsmith', 'drill ground', 'career center', 'armorsmith', 'scout camp', 'stable']);
				GM.debug("4. Need food " + this.needFood + " minc " + minC);	
				if (minC != '' && !did) 
					did = this.Build('city', minC, 'create');

				if (!did) {
					// pick only if they are less than above, or don't exist
					var minC = this.MinCityLev(['bunker','tower','city wall','market'],minC);
					GM.debug("5. Need food " + this.needFood + " minc " + minC);
					if (minC != '') 
						did = this.Build('city', minC, 'create');
				}
			}

			this.needWarehouse = ((this.stats.clay.cur >= this.stats.clay.max*.98) || (this.stats.lumber.cur>=this.stats.lumber.max*.98));

			if (this.stats.lumber.max > this.stats.crop.max) {
				if (!did) did = this.Build('city', 'granary')
			} else {
				if (!did) did = this.Build('city', 'warehouse');
			}
			
			if (!did) {
				this.JustDidIt('AutoCity');
			}
		}		
	}

	if (!did) {
		GM.setLValue('BuildCheckList', '');
	}

	//GM.debug("Returning " + did + " from MainBuild");
	return did;
},
Build:function(nav, name) {
	var buildCheckList = GM.getLValue('BuildCheckList', '');

	if (buildCheckList.indexOf(nav+':'+name)>=0) {
		//GM.debug("here 0 " + name);
		return false;
	}

	if (this.PickArea(nav, name))
		return true;

	//GM.debug("here 1 " + name);
			
	if (!this.navOK) {
		GM.setLValue('BuildCheckList', buildCheckList + ',' + nav+':'+name);
		return false;
	}	
	//GM.debug("here 2 " + name);

	var pop = this.currentPop;
	if (pop) {
		var title = this.currentPopName;
		
		var up  = nHtml.FindByXPath(pop,".//a[contains(@onclick,'build.upgrade')]");
		if (!up) 
			up = nHtml.FindByXPath(pop,".//a[@sgtitle='Upgrade']");

		//GM.debug("here 3 " + name +  " bcs " + buildCheckList);

		if (!up) {
			GM.log("No upgrade button for " + title);
			GM.setLValue('BuildCheckList', buildCheckList + ',' + nav+':'+name);
			return this.CloseFloat();
		}

		var reqs = {};
		nHtml.LoopByXPath(pop,".//li/img",function (el) {
			var req=String(el.getAttribute('sgtitle'));
			req=req.trim().toLowerCase();
			var val=nHtml.GetText(el.parentNode).trim();
			reqs[req] = val;
		});

		if (this.stats.lumber.cur >= reqs.wood &&
		    this.stats.clay.cur   >= reqs.stone &&
		    this.stats.iron.cur   >= reqs.iron &&
		    this.stats.crop.cur   >= reqs.food) {  
		    this.Click(up);
		    GM.debug("here 4 " + name +  " bcs " + buildCheckList);
		    return true;
		}

		GM.log("Wait to upgrade " + name);
		GM.setLValue('BuildCheckList', buildCheckList + ',' + nav+':'+name);
		return this.CloseFloat();
	} else {
		GM.log("No pop for " + name);
		GM.setLValue('BuildCheckList', buildCheckList + ',' + nav+':'+name);
		return false;
	}
},
PickArea:function(nav, name) {
	var ext = arguments.callee ? (" called from " + arguments.callee.caller.name) : '';
	GM.debug("Pick area " + nav + " : " + name + ext);
	
	if (this.Goto(nav)) 
		return true;
		
        if (!this.navOK) {
		GM.log("Not the nav I expect to be " + this.currentNav + " != " + nav);
		return this.CloseFloat();
	}

	if (name == undefined)
		name = '';
	
	// already at area
	var map;
	var pop;
	
	if (nav == 'resource') {
		map = nHtml.FindByAttrContains(document.body, "map", "id", 'Res');
		pop = nHtml.FindByXPath(document.body, ".//div[@id='dialog']");
	} else if (nav == 'city') {
		map = nHtml.FindByAttrContains(document.body, "map", "id", 'city');
		pop = nHtml.FindByXPath(document.body, ".//div[@id='floatblockleft']");
	}
	
	if (!nHtml.IsVisible(pop)) 
		pop = null;
	
	this.currentPop=null;
	this.currentPopName='';
	this.cityStats = {};

	var cityStr = GM.getLValue("CityStats", '');
		
	//GM.log("cityStr is " + cityStr);
	var dsp=cityStr.split(','); 
	for (var j in dsp) {
		var vent=dsp[j];
		if (vent != '') {
			var p2 = vent.indexOf('=');
			var k = vent.substr(0,p2);
			var v = vent.substr(p2+1);
			//GM.log("city k=v is " + k + '=' + v);
			this.cityStats[k]=parseInt(v);
		}
	}

	this.navOK=false;
	
	if (!map) {
		GM.log("Can't find map for " + nav);
		return false;
	}
	
	if (pop && nHtml.IsVisible(pop)) {
		this.currentPop=pop;
		var title = nHtml.FindByXPath(pop, ".//h4 | .//h2");			
		if (title) {
			title=nHtml.GetText(title).trim().toLowerCase();
			this.currentPopName=title;
			GM.log("Current pop is " + title);
		}
		if (title && title.indexOf(name)==0) {
			this.currentPopName=name;	// normalize to be nice
			this.navOK=true;		// need to check this!
			return false;
		} else {
			if (title) {
				GM.log("Not the area I expect to be " + this.currentPopName + " != " + name);
				return this.CloseFloat();
			}
			return false;
		}
	}

	if (!map) {
		GM.log("Pick while not in area");
		return false;
	}
	
	this.currentMap = map;

	// pick lowest lev
	var minLv = 10000;
	var minEl;
	nHtml.LoopByXPath(map, ".//area", function(el) {
		var d='';
		var ename = el.getAttribute('sgtitle').trim().toLowerCase();
		var lv = ename.match(/(.*)lv\.(\d+)/);
		if (lv) {
			ename = lv[1].trim();
			lv = lv[2];
			//GM.debug("ename: " + ename + " lv: " + lv);
			Player.cityStats[ename] = parseInt(lv);
			if (name && (ename.indexOf(name) == 0)) {
				if (lv < minLv) {
					GM.debug("Found: " + ename + " lv: " + lv);
					minEl = el;
					minLv = lv;
				}
			}
		}
	});

	cityStr = '';
	for (var x in this.cityStats) {
		cityStr = cityStr + x + "=";
		cityStr = cityStr + this.cityStats[x] + ",";
	}
	if (cityStr != '') {
		GM.debug("City stats : " + cityStr);
		GM.setLValue("CityStats", cityStr);
	}

	if (name && name != undefined) {
		if (minEl) {	
			GM.debug("Click on building " + minEl.getAttribute('sgtitle'));
			Player.Click(minEl);
			return true;
		}	
		GM.log("Can't find area " + name);
	} else {
		GM.debug("Don't need area");
		// just wanted cityStats
		this.navOK = true;
	}
	return false;	
},
// slowly, after a randomized time click a button
Click:function(button) {
	this.waitMilliSecs=4000+Math.random()*2000;
	nHtml.Click(button);
},
UnsafeClick:function(obj) {
	nHtml.setTimeout(function() {
		if (obj.getAttribute('onclick') && obj.getAttribute('onclick').indexOf('MM_') == 0) {
					var code = obj.getAttribute('onclick');
					eval('unsafeWindow.'+code);
			GM.log("oncl code is " + code);
		} else if (obj.href && obj.href.indexOf('javascript:MM_') == 0) {
			var code = obj.href.substr(11);
			eval('unsafeWindow.'+code);	
			GM.log("js code is " + code);
		} else {
			GM.log("Click: no code for obj");
		}
	},1000+Math.floor(Math.random()*1000));
},
GetMainNav:function() {
	return nHtml.FindByXPath(document.body, ".//div[contains(@class,'mainnav')]");
},
Goto:function(nav) {
	nav = nav.toLowerCase();
	if (nav == 'city') {
		if (nHtml.FindByXPath(document.body, ".//map[@id='citymap']")) {
			this.currentNav = nav;
			this.navOK = true;
			return false;
		}
	} else if (nav == 'resource') {
		if (nHtml.FindByAttrContains(document.body, 'map', 'id', 'Res')) {
			this.currentNav = nav;
			this.navOK = true;
			return false;
		}
	} else {
		if (nHtml.FindByXPath(document.body, ".//div[@class='subwindow_header']/h2[contains(translate(text(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),'"+nav+"')]")) {
			this.currentNav = nav;
			this.navOK = true;
			return false;
		}
	}

	GM.debug("Nav to " + nav);
	
	this.navOK = false;
	
	var mn = this.GetMainNav();
	var a = nHtml.FindByXPath(mn,".//a[contains(translate(@sgtitle,'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),'"+nav+"')]");
	if (a) {
		GM.debug("Click on nav " + nav);
		this.Click(a);
		return true;
	} else {
		this.currentNav = 'error';
		GM.log("Can't find nav " + nav);		
		return false;
	}
},
WaitMainLoop:function() {
	nHtml.waitForPageChange=true;	// prevent multiple loops
	nHtml.setTimeout(function() { if (nHtml.waitForPageChange) {nHtml.waitForPageChange=false; Player.MainLoop();} },this.waitMilliSecs);
},

ReloadOccasionally:function() {
	nHtml.setTimeout(function() {
		Player.ReloadNow();
	}, 1000*60*45 + (60 * 45 * 1000 * Math.random()));
},

ReloadNow:function() {
	GM.log('Page reloaded');
	window.location = window.location;
	nHtml.clearTimeouts();
	Player.ReloadOccasionally();
}

};

jsAddFunctionNames(Player, 'Player');
jsAddFunctionNames(nHtml, 'nHtml');
jsAddFunctionNames(GM, 'GM');

Player.ReloadOccasionally();

function jsAddFunctionNames (o, n) {
	for (var f in o) {
		if (typeof(o[f]) == 'function') {
			o[f].name = n + (n?'.':'') + f;
		}
	}
}