BOT_VERSION = 3.29;

local handle = io.popen("SubWCRev \""..getExecutionPath().."\"")
local result = handle:read("*a")
handle:close()
BOT_REVISION = string.match(result,"Updated to revision (%d*)") or "<UNKNOWN>"

--[[
BOT_REVISION = "<UNKNOWN>"

-- Check version 1.7 style svn folder.
local fname = getExecutionPath() .. "/.svn/wc.db"
if( fileExists(fname) ) then
	local file, err = io.open(fname, "rb");
	if file then
		local string = file:read("*a")
		for ver in string.gmatch(string,"%(svn:wc:ra_dav:version.url %d* %/svn%/!svn%/ver%/(%d*)%/trunk%/rom%)") do
			if BOT_REVISION == "<UNKNOWN>" or ver > BOT_REVISION then
				BOT_REVISION = ver
			end
		end
		file:close();
	end
end

-- If not found, try version 1.6 style svn folder.
if BOT_REVISION == "<UNKNOWN>" then
	local fname = getExecutionPath() .. "/.svn/entries"
	if( fileExists(fname) ) then
		local dirfound = false
		for line in io.lines(fname) do
			if dirfound then BOT_REVISION = line break elseif line == "dir" then dirfound = true end
		end
	end
end
]]

include("addresses.lua");
include("database.lua");
include("functions.lua");
include("classes/player.lua");
include("classes/equipment.lua");
include("classes/inventory.lua");
include("classes/bank.lua");
include("classes/guildbank.lua");
include("classes/cursor.lua");
include("classes/camera.lua");
include("classes/waypoint.lua");
include("classes/waypointlist.lua");
include("classes/waypointlist_wander.lua");
include("classes/node.lua");
include("classes/object.lua");
include("classes/objectlist.lua");
include("classes/eggpet.lua");
include("classes/store.lua");
include("classes/party.lua");
include("classes/itemtypes.lua");
include("classes/pet.lua");
include("settings.lua");
include("macros.lua");

functionBeingLoaded = "romglobal/userfunctions.lua"
if( fileExists(getExecutionPath().."/../romglobal/userfunctions.lua") ) then
	include("../romglobal/userfunctions.lua");
end

functionBeingLoaded = "userfunctions.lua"
if( fileExists(getExecutionPath().."/userfunctions.lua") ) then
	include("userfunctions.lua");
end

functionBeingLoaded = nil -- In case the user has no userfunctions.

-- Allow include to check global and relative folders too
if originalInclude == nil then
	originalInclude = include
end
function include(file, forceInclude)
	return originalInclude(findFile(file), forceInclude)
end

functionBeingLoaded = nil -- In case the user has no userfunctions.

printf("Installing userfunctions. ") -- Message to help tell when userfunctions are a problem
-- Install bot userfunctions
local addondir = getDirectory(getExecutionPath() .. "/userfunctions/");
for i,v in pairs(addondir) do
	local match = string.match(v, "addon_(.*)%.lua");
	if( not match ) then match = string.match(v, "userfunction_(.*)%.lua"); end;
	if( match ~= nil ) then
		functionBeingLoaded = match
		include("userfunctions/" .. v);
		functionBeingLoaded = nil
		logMessage("Bot userfunction \'" .. match .. "\' successfully loaded.");
	end
end

-- Install global bot userfunctions
local globaladdondir = getDirectory(getExecutionPath() .. "/../romglobal/userfunctions/");
if globaladdondir then
	local localdir = getExecutionPath() .. "/userfunctions/"
	for i,v in pairs(globaladdondir) do
		if not(fileExists(localdir .. v)) then -- if not in local 'userfunctions'
			local match = string.match(v, "addon_(.*)%.lua");
			if( not match ) then match = string.match(v, "userfunction_(.*)%.lua"); end;
			if( match ~= nil ) then
				functionBeingLoaded = match
				include("../romglobal/userfunctions/" .. v);
				functionBeingLoaded = nil
				logMessage("Global bot userfunction \'" .. match .. "\' successfully loaded.");
			end
		end
	end
end
printf("\n") -- Return to indicate end of userfunction installation. User wont notice this but
             -- if error is on same line as "Installing userfunctions" then we know it a userfunction error.

-- Check for old userfunction_questbyname
if compareQuestnames then
	error("The QuestByName functions are now part of the bot. The old userfunction is obsolete. Please remove the 'userfunctions_questbyname.lua' file from the userfunctions folder.")
end

setPriority(priority.high);

settings.load();
setStartKey(settings.hotkeys.START_BOT.key);
setStopKey(settings.hotkeys.STOP_BOT.key);



__WPL = nil;	-- Way Point List
__RPL = nil;	-- Return Point List

-- start message
text = sprintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" ..
	"Welcome to rom bot! press END to quit\n" ..
	"RoM Bot Version %0.2f, Revision %s\n", BOT_VERSION, BOT_REVISION);

printPicture("logo", text, 4);

function main()
	local forcedProfile = nil;
	local forcedPath = nil;
	local forcedRetPath = nil;
	local forcedCharacter = nil;
	local debug_override = false

	for i = 2,#args do
		args[i] = string.lower(args[i])
		if( args[i] == "update" ) then
			include("update.lua");
		end

		if( args[i] == "debug" ) then
			debug_override = true
			settings.options.DEBUGGING = true;
			settings.options.DEBUGGING_MACRO = true;

			-- adds the numbers to the prints while in debug mode,
			-- normal language code is in settings.lua

			if( settings.options.USE_CLIENT_LANGUAGE ) then
				local hf_language;
				if( bot.ClientLanguage == "DE" ) then
					hf_language = "deutsch";
				elseif(bot.ClientLanguage  == "FR" ) then
					hf_language = "french";
				elseif(bot.ClientLanguage  == "RU" ) then
					hf_language = "russian";
				elseif(bot.ClientLanguage == "PL" ) then
					hf_language = "polish";
				else
					hf_language = "english";
				end
				local function setLanguage(_name)
					include("/language/" .. _name .. ".lua");
				end
				local lang_base = {};
				for i,v in pairs(language) do lang_base[i] = v; end;	-- remember current language value to fill gaps with that
				setLanguage(hf_language);
				for i,v in pairs(lang_base) do
					if( language[i] == nil ) then
						language[i] = "["..i.."] "..v;
					end
				end;
				lang_base = nil; -- Not needed anymore, destroy it.
				logMessage("Load Language for debugging with numbers");
			end
		end

		local foundpos = string.find(args[i], ":", 1, true);
		if( foundpos ) then
			local var = string.sub(args[i], 1, foundpos-1);
			local val = string.sub(args[i], foundpos+1);

			if( var == "profile" ) then
				forcedProfile = val;
			elseif( var == "path" ) then
				forcedPath = val;
			elseif( var == "retpath" ) then
				forcedRetPath = val;
			elseif( var == "character") then
				forcedCharacter = val;
			else
				-- invalid option
				local msg = sprintf(language[61], args[i]);
				error(msg, 0 );
			end
		end

		-- check the options
		if(not foundpos  and  args[i] ~= "update" and args[i] ~= "debug" ) then
			local msg = sprintf(language[61], args[i]);
			error(msg, 0 );
		end;

	end

	database.load();
	attach(getWin(forcedCharacter));

	if( not checkExecutableCompatible() ) then
		printf("Addresses seem off, trying to update.\n")
		include("update.lua")
		if( not checkExecutableCompatible() ) then
			error("The addresses didn't update, you will have to wait until there is an SVN update that updates the addresses.\n")
		else
			cprintf(cli.yellow,"update.lua seems to have worked, addresses.lua has been updated.\n")
		end
	end

	local playerAddress = memoryReadUIntPtr(getProc(), addresses.staticbase_char, addresses.charPtr_offset);
	if( settings.options.DEBUGGING ) then
		printf(language[44]);	-- Attempt to read playerAddress
	end

	if( playerAddress == nil ) then
		local msg = sprintf(language[48], "playerAddress");	-- pls update to current version
		error(msg, 0);
	end;
	logMessage(sprintf("Using static char address 0x%X, player address 0x%X",
		tonumber(addresses.staticbase_char), tonumber(playerAddress)));

	equipment = CEquipment()
	player = CPlayer.new();

	if( settings.options.DEBUGGING ) then
		-- Player debugging info
		printf("[DEBUG] playerAddr: 0x%X\n", player.Address);
		printf("[DEBUG] player classes: %d/%d\n", player.Class1, player.Class2);
		printf("[DEBUG] player pet: 0x%X\n", player.PetPtr);
		printf("[DEBUG] Player target: 0x%X\n", player.TargetPtr);

		if( player.TargetPtr ~= 0 ) then
			local target = CPawn(player.TargetPtr);
			printf("[DEBUG] player target type: 0x%X\n", target.Type);
			printf("[DEBUG] player target attackable: %s\n", target.Attackable);
			printf("[DEBUG] player target aggressive: %s\n", target.Aggressive);
		end

		printf("[DEBUG] player in battle: %s\n", tostring(player.Battling));
	end


	if( settings.options.DEBUGGING ) then
		-- Mouse pawn debugging info
		local mousePawn = CPawn( memoryReadUIntPtr(getProc(), addresses.staticbase_char, addresses.mousePtr_offset) );
		printf("[DEBUG] mousePawn: 0x%X\n", mousePawn.Address);
		printf("[DEBUG] mousePawn id: %d\n", mousePawn.Id);
	end

	camera = CCamera();
	if( settings.options.DEBUGGING ) then
		printf("[DEBUG] camAddress: 0x%X\n", camera.Address);
	end

	if( settings.options.DEBUGGING ) then
		-- Camera debugging info
		printf("[DEBUG] Cam X: %0.2f, Y: %0.2f, Z: %0.2f\n", camera.X, camera.Y, camera.Z);
		printf("[DEBUG] Cam XU: %0.2f, YU: %0.2f, ZU: %0.2f\n", camera.XUVec, camera.YUVec, camera.ZUVec);
	end

	cprintf(cli.turquoise, "Game Version is %s\n", getGameVersion())
	local hf_x, hf_y, hf_wide, hf_high = windowRect( getWin());
	cprintf(cli.turquoise, language[42], hf_wide, hf_high, hf_x, hf_y );	-- RoM windows size

	-- convert player name to profile name and check if profile exist
	local load_profile_name;	-- name of profile to load
	if( forcedProfile ) then
		load_profile_name = convertProfileName(forcedProfile);
	else
		load_profile_name = convertProfileName(player.Name);
	end

	-- Set window name, install timer to automatically do it once a second
	local displayname = player.Name
	if #displayname > 8 then
		displayname = string.sub(displayname, 1, 7) .. "*";
	end
	setWindowName(getHwnd(), sprintf("RoM Bot %s [%s]", BOT_VERSION, displayname));
	settings.loadProfile(load_profile_name);
	if RoMScript("GC_GetMouseMoveEnable()") == false then
		cprintf(cli.yellow, language[190],"Click to Move") -- The bot requires 'Click to Move'
		RoMCode("GC_SetMouseMoveEnable(true)")
	end
	if RoMScript("GC_GetSelfCastEnable()") == false then
		cprintf(cli.yellow, language[190],"Self Cast") -- The bot requires 'Self Cast'
		RoMCode("GC_SetSelfCastEnable(true)")
	end
	if debug_override == true then -- Needs to be after loading profile.
		--settings.profile.options.DEBUG_INV = true;
		settings.profile.options.DEBUG_LOOT = true;
		settings.profile.options.DEBUG_TARGET = true;
		settings.profile.options.DEBUG_HARVEST = true;
		settings.profile.options.DEBUG_WAYPOINT = true;
		settings.profile.options.DEBUG_AUTOSELL = true;
	end
	player:update()
	settingsPrintKeys();		-- print keyboard settings to MM and log
	registerTimer("timedSetWindowName", secondsToTimer(1), timedSetWindowName, load_profile_name);
	player.BotStartTime_nr = os.time();	-- remember bot start time no reset
	player.level_detect_levelup = player.Level;	-- remember actual player level

	store = CStore()

	if( getTimerFrequency ) then
		-- Grab the real frequency instead of calculating it, if available
		bot.GetTimeFrequency = getTimerFrequency().low / 1000;
	else
		-- calculate the CPU Frequency / used for manipulation the GetTime() values
		local calc_start = getTime();
		yrest(1000);
		local calc_end = getTime();
		bot.GetTimeFrequency = (calc_end.low - calc_start.low) / 1000;
	end

	printf("[DEBUG] CPU Frequency %s\n", bot.GetTimeFrequency);

	inventory = CInventory();		-- register inventory (needs profile loaded because of maxslot)
	bank = CBank()
	guildbank = CGuildbank()
	cursor = CCursor()

	LoadItemTypes()     -- Needs macros to already be set up.


	-- list waypoint files and files in folders
	-- only files with filetype '.xml' are listed
	-- only folders without '.' are listed
	-- only 1 level of subfolders will be listed
	local current_folder = ""
	local show_subfiles = settings.profile.options.WPL_SHOW_SUBFOLDER_FILES
	if show_subfiles ~= true then show_subfiles = false end -- default true

	local function list_waypoint_files()

		local hf_counter = 0;
		local pathlist = { }

		local function read_subdirectory(_folder)
			local tmpfolder = _folder
			if current_folder ~= "" then
				tmpfolder = current_folder .. "/" .. _folder
			end
			local subdir = getDirectory(getExecutionPath() .. "/waypoints/" .. tmpfolder)

			if( not subdir) then return; end
			if show_subfiles then
				for i,v in pairs(subdir) do
					if( string.find (v,".xml",1,true) ) then
						hf_counter = hf_counter + 1;
						pathlist[hf_counter] = { };
						pathlist[hf_counter].folder = _folder;
						pathlist[hf_counter].filename = v;
					end
				end
			else
				hf_counter = hf_counter + 1
				pathlist[hf_counter] = { };
				pathlist[hf_counter].folder = _folder;
			end

		end		-- end of: local function read_subdirectory(_folder)


		local function concat_filename(_i, _folder, _filename)

			local hf_newname;
			local hf_folder = "";
			local hf_dots = "";
			local hf_slash = "";

			if _folder  then
				if _filename then
					if string.len(_folder) > 8 then
						hf_folder = string.sub(_folder, 1, 6);
						hf_dots = "..";
						hf_slash = "/";
					elseif( _folder  and  string.len(_folder) > 0 )  then
						hf_folder = _folder;
						hf_slash = "/";
					end
				else
					if string.len(_folder) > 20 then
						hf_folder = string.sub(_folder, 1, 18);
						hf_dots = "..";
						hf_slash = "/";
					elseif( _folder  and  string.len(_folder) > 0 )  then
						hf_folder = _folder;
						hf_slash = "/";
					end
				end
			end

			hf_newname = sprintf("%s%s%s%s",
			  hf_folder,
			  hf_dots,
			  hf_slash,
			  _filename or "");

			hf_nr = sprintf("%3d:", _i);

			return hf_nr, hf_newname;

		end

		-- Get file info from waypoints folder
		local dir = getDirectory(getExecutionPath() .. "/waypoints/" .. current_folder)

		-- copy table dir to table pathlist
		-- select only xml files
		pathlist[0] = { };
		if current_folder == "" then
			pathlist[0].filename = "wander";
			pathlist[1] = { filename = "resume" };
			hf_counter = 1
		else
			pathlist[0].folder = ".."
		end

		for i,v in pairs(dir) do
			-- no . means perhaps folder
			if( not string.find (v,".",1,true) ) then
				read_subdirectory(v);

			-- only list files with extension .xml
			elseif( string.find (v,".xml",1,true) ) then
				hf_counter = hf_counter + 1;
				pathlist[hf_counter] = { };
				pathlist[hf_counter].filename = v;
			end
		end
		local last_local = hf_counter

		if current_folder == "" then
			-- Get file info from global waypoints folder
			local globdir = getDirectory(getExecutionPath() .. "/../romglobal/waypoints")

			if globdir then
				-- copy table dir to table pathlist
				-- select only xml files
				for i,v in pairs(globdir) do

					-- no . means perhaps folder
					if( not string.find (v,".",1,true) ) then
						read_subdirectory("../../romglobal/waypoints/"..v);

					-- only list files with extension .xml
					elseif( string.find (v,".xml",1,true) ) then
						hf_counter = hf_counter + 1;
						pathlist[hf_counter] = { };
						pathlist[hf_counter].folder = "../../romglobal/waypoints";
						pathlist[hf_counter].filename = v;
					end
				end
			end
		end

		-- Print local waypoint files
		cprintf(cli.green, language[144], getExecutionPath());	-- Waypoint files in %s
		if current_folder ~= "" then print("Sub folder: "..current_folder) end

		local inc = math.ceil((last_local+1)/3);
		for i = 0, inc - 1 do

			local column1 = ""; local column2 = ""; local column3 = "";
			local col1nr = ""; local col2nr = ""; local col3nr = "";

			col1nr, column1 = concat_filename(i, pathlist[i].folder, pathlist[i].filename)

			if ( (i + inc) <= last_local ) then
				col2nr, column2 = concat_filename(i+inc, pathlist[i+inc].folder, pathlist[i+inc].filename);
			end
			if ( (i+inc*2) <= last_local ) then
				col3nr, column3 = concat_filename(i+inc*2, pathlist[i+inc*2].folder, pathlist[i+inc*2].filename);
			end

			cprintf(cli.green,"%s %s %s %s %s %s\n",
				col1nr,
				string.sub(column1.."                    ", 1, 21),
				col2nr,
				string.sub(column2.."                    ", 1, 21),
				col3nr,
				string.sub(column3.."                    ", 1, 20) );

		end

		-- Print global waypoint files
		if #pathlist > last_local then
			cprintf(cli.green, language[144], getExecutionPath() .. "/../romglobal");	-- Waypoint files in %s

			local inc = math.ceil((#pathlist-last_local)/3);
			for i = last_local + 1, last_local + inc do

				local column1 = ""; local column2 = ""; local column3 = "";
				local col1nr = ""; local col2nr = ""; local col3nr = "";

				col1nr, column1 = concat_filename(i, string.gsub(pathlist[i].folder,"%.%./%.%./romglobal/waypoints/*",""), pathlist[i].filename)

				if ( (i + inc) <= #pathlist ) then
					col2nr, column2 = concat_filename(i+inc, string.gsub(pathlist[i+inc].folder,"%.%./%.%./romglobal/waypoints/*",""), pathlist[i+inc].filename);
				end
				if ( (i+inc*2) <= #pathlist ) then
					col3nr, column3 = concat_filename(i+inc*2, string.gsub(pathlist[i+inc*2].folder,"%.%./%.%./romglobal/waypoints/*",""), pathlist[i+inc*2].filename);
				end

				cprintf(cli.green,"%s %s %s %s %s %s\n",
					col1nr,
					string.sub(column1.."                    ", 1, 21),
					col2nr,
					string.sub(column2.."                    ", 1, 21),
					col3nr,
					string.sub(column3.."                    ", 1, 20) );

			end
		end

		-- ask for pathname to choose
		keyboardBufferClear();
		io.stdin:flush();
		cprintf(cli.green, language[145], getKeyName(_G.key.VK_ENTER) );	-- Enter the number of the path
		local hf_choose_path_nr = io.stdin:read()
		if hf_choose_path_nr == "q" then
			error("Quiting.",0)
		end
		hf_choose_path_nr = tonumber(hf_choose_path_nr)
		if( hf_choose_path_nr and
			hf_choose_path_nr >= 0 and
			hf_choose_path_nr <= #pathlist ) then
			printf(language[146], hf_choose_path_nr );	-- You choose %s\n
			if pathlist[hf_choose_path_nr].filename then
				if( pathlist[hf_choose_path_nr].folder ) then
					wp_to_load = pathlist[hf_choose_path_nr].folder.."/"..pathlist[hf_choose_path_nr].filename;
				else
					if current_folder == "" then
						wp_to_load = pathlist[hf_choose_path_nr].filename;
					else
						wp_to_load = current_folder .. "/" .. pathlist[hf_choose_path_nr].filename;
					end
				end
			else
				if pathlist[hf_choose_path_nr].folder == ".." then
					current_folder = string.match(current_folder,"(.*)/[^/]*$") or ""
					if string.lower(current_folder) == "/../../romglobal/waypoints" then current_folder = "" end
				else
					current_folder = current_folder .. "/" .. pathlist[hf_choose_path_nr].folder
				end
			end
			return wp_to_load;
		else
			local filename = fixSlashes(getOpenFileName(getExecutionPath().."/waypoints/"))
			if filename == "" then
				cprintf(cli.yellow, language[147]);	-- Wrong selection
				yrest(3000);
				return false;
			else
				-- Convert to relative path
				local wpfolder = {}
				for folder in string.gmatch(getExecutionPath().."/waypoints/","([^/]+)") do
					table.insert(wpfolder,folder)
				end
				local fnfolder = {}
				for folder in string.gmatch(filename,"([^/]+)") do
					table.insert(fnfolder,folder)
				end
				if fnfolder[1] ~= wpfolder[1] then
					cprintf(cli.yellow, language[191]);	-- The RoMBot currently only supports running waypoint files located on the same drive as MicroMacro.
					yrest(3000);
					return false
				end
				local fileNameStart = ""
				local fileNameEnd = table.copy(fnfolder)
				local changefound = false
				for k,v in pairs(wpfolder) do
					if fnfolder[k] == v and changefound == false then
						table.remove(fileNameEnd,1)
					else
						changefound = true
						fileNameStart = fileNameStart .. "../"
					end
				end

				return fileNameStart .. table.concat(fileNameEnd,"/")
			end
		end

	end
	-- end of local function list_waypoint_files()


	-- This logic prevents files from being loaded if wandering was forced
	local wp_to_load, rp_to_load;
	-- get wp filename to load
	if( forcedPath ) then			-- waypointfile or 'wander'
		local filename = getExecutionPath() .. "/waypoints/" .. string.gsub(forcedPath,".xml","") .. ".xml";
		local globalfilename = getExecutionPath() .. "/../romglobal/waypoints/" .. string.gsub(forcedPath,".xml","") .. ".xml";
		local resumelogname = getExecutionPath() .. "/logs/resumes/"..player.Name..".txt"

		if fileExists(filename) or ( string.lower(forcedPath) == "wander" ) or
				( string.lower(forcedPath) == "resume" and fileExists(resumelogname) ) then
			wp_to_load = forcedPath;
		elseif fileExists(globalfilename) then
			wp_to_load = "../../romglobal/waypoints/"..forcedPath;
		else
			cprintf(cli.yellow,language[153], filename ); -- We can't find your waypoint file
		end;
	else
		if( settings.profile.options.WAYPOINTS and __WPL == nil ) then
			wp_to_load = settings.profile.options.WAYPOINTS;
		end
	end

	-- get rp filename to load
	if( forcedRetPath ) then
		rp_to_load = forcedRetPath;
	else
		if( settings.profile.options.RETURNPATH and __RPL == nil ) then
			rp_to_load = settings.profile.options.RETURNPATH;
		end
	end

	-- set wander if defined in profile
	if( settings.profile.options.PATH_TYPE == "wander") then
	    wp_to_load = "wander";
	end

	-- list the path list?
	-- if we don't have a wp file to load, list them
	if( __WPL == nil ) then		-- not allready loaded (in onLoad event)

		while( wp_to_load == nil or wp_to_load == "" or wp_to_load == false	or wp_to_load == " " ) do
			wp_to_load = list_waypoint_files();
		end;
		if( wp_to_load == "wander" ) then
			cprintf(cli.lightgray,language[187])
			keyboardBufferClear();
			io.stdin:flush();
			local radius = tonumber(io.stdin:read())
			if type(radius) ~= "number" or 0 > radius then
				cprintf(cli.lightgray,"Expecting number more than or equal to 0. Using default value.\n")
				yrest(2000)
			else
				settings.profile.options.WANDER_RADIUS = radius
			end

			loadPaths("wander", rp_to_load);
		else
			loadPaths(wp_to_load, rp_to_load);	-- load the waypoint path / return path
		end;
	end;

	player:updateXYZ() -- update player coords

	-- special option for use waypoint file from profile in a reverse order / not if forced path
	if( settings.profile.options.WAYPOINTS_REVERSE == true  and
	    not forcedPath  ) then
		__WPL:reverse();
	end;

	-- look for the closest waypoint / return path point to start
	if( __RPL and __WPL.Mode ~= "wander" ) then	-- return path points available ?
		-- compare closest waypoint with closest returnpath point
		__WPL:setWaypointIndex( __WPL:getNearestWaypoint(player.X, player.Z, player.Y ) );
		local hf_wp = __WPL:getNextWaypoint();
		local dist_to_wp = distance(player.X, player.Z, player.Y, hf_wp.X, hf_wp.Z, hf_wp.Y)

		__RPL:setWaypointIndex( __RPL:getNearestWaypoint(player.X, player.Z, player.Y ) );
		local hf_wp = __RPL:getNextWaypoint();
		local dist_to_rp = distance(player.X, player.Z, player.Y, hf_wp.X, hf_wp.Z, hf_wp.Y)

		if( dist_to_rp < dist_to_wp ) then	-- returnpoint is closer then next normal wayoiint
			player.Returning = true;	-- then use return path first
			cprintf(cli.yellow, language[12]);	-- Starting with return path
		else
			player.Returning = false;	-- use normale waypoint path
		end;
	else
		if __WPL.ResumePoint then
			__WPL:setWaypointIndex(__WPL.ResumePoint)
		else
			__WPL:setWaypointIndex( __WPL:getNearestWaypoint(player.X, player.Z, player.Y ) );
		end
	end;

	-- Update inventory
	inventory:update();

	-- Profile onLoad event
	-- possibility for users to overwrite profile settings
	if( type(settings.profile.events.onLoad) == "function" ) then
		local status,err = pcall(settings.profile.events.onLoad);
		if( status == false ) then
			local msg = sprintf("onLoad error: %s", err);
			error(msg);
		end
	end

	-- Remember login details
	CurrentLoginAcc = RoMScript("LogID")
	CurrentLoginChar = RoMScript("CHARACTER_SELECT.selectedIndex")
	CurrentLoginServer = RoMScript ("GetCurrentRealm()")

	local function checkClientCrash()
		if isClientCrashed() then
			if onClientCrash then
				print("Client crash detected. Running onClientCrash().")
				onClientCrash()
			else
				error("Client crash detected.")
			end
		end
	end
	registerTimer("ClientDetection", secondsToTimer(2), checkClientCrash);

	local distBreakCount = 0; -- If exceedes 3 in a row, unstick.
	while(true) do
		while __WPL.DoOnload do
			__WPL.DoOnload = false
			__WPL.onLoadEvent()
		end
		if __WPL.Mode == "waypoints" and #__WPL.Waypoints == 0 then -- Can't got to 'waypoints' with no waypoints
			error(language[114],1) -- No waypoints to go to
		end

		player:checkAddress()
		player:logoutCheck();
		player.Fighting = false;		-- we are now not in the fight routines

		player:updateAlive()
		if( not player.Alive ) then
			player:resetSkillLastCastTime();	-- set last use back, so we can rebuff
			resurrect();
		end

		-- reloading ammunition
		if ( settings.profile.options.RELOAD_AMMUNITION ) then
			local ammo = string.lower(settings.profile.options.RELOAD_AMMUNITION);
			if  ammo == "thrown" and (player.Class1 == CLASS_ROGUE or (player.Class2 == CLASS_ROGUE and player.Level > 11 and player.Level2 > 11)) then
				if inventory:getAmmunitionCount() == 0 then
					inventory:reloadAmmunition(ammo);
				end
			elseif ammo == "arrow" and (player.Class2 == CLASS_SCOUT or player.Class1 == CLASS_SCOUT) then
				if inventory:getAmmunitionCount() == 0 then
					inventory:reloadAmmunition(ammo);
				end
			else
			    print("RELOAD_AMMUNITION can only be false, arrow or thrown!");
			end
		end


		-- go back to sleep, if in sleep mode
		if( player.Sleeping == true ) then
			yrest(800);	-- wait a little for the aggro flag
			player:updateBattling();
			if( player.Battling == false ) then
				player:sleep();
			end;
		end;	-- go sleeping if sleeping flag is set


		-- trigger timed inventory update
		--if( os.difftime(os.time(), player.InventoryLastUpdate) >
			--settings.profile.options.INV_UPDATE_INTERVAL ) then
			--player.InventoryDoUpdate = true;
		--end

		-- update inventory if update flag is set
		-- TODO: rolling update while resting?
		player:updateBattling();
		if(player.InventoryDoUpdate == true and
		   not player.Battling ) then
			player.InventoryDoUpdate = false;
			player.InventoryLastUpdate = os.time();		-- remember update time
			inventory:update();
		end;


		-- check if levelup happens / execute after aggro is gone
		-- we do it here , to be sure, aggro flag is gone
		--  aggro flag would needs a wait (if no loot), so we don't check it
		player:updateLevel()
		if(player.Level > player.level_detect_levelup   and
		   not player.Battling )  then

			player.level_detect_levelup = player.Level;

			-- check if onLevelup event is used in profile
			if( type(settings.profile.events.onLevelup) == "function" ) then
				local status,err = pcall(settings.profile.events.onLevelup);
				if( status == false ) then
					local msg = sprintf(language[75], err);
					error(msg);
				end
			end

			settings.updateSkillsAvailability()     -- Also needs macros to already be set up.
		end


		-- rest after getting new target and before starting fight
		-- rest between 50 until 99 sec, at most until full, after that additional rnd(10)
		if player.Current_waypoint_type ~= WPT_TRAVEL then	-- no resting if running waypoin type
			player:updateHP()
			player:updateMP()

			local manaRest, healthRest = false, false;
			if( player.MaxMana > 0 ) then
				manaRest = (player.Mana / player.MaxMana * 100) < settings.profile.options.MP_REST;
			end
			healthRest = (player.HP / player.MaxHP * 100) < settings.profile.options.HP_REST;

			if( manaRest or healthRest ) then
					player:rest( 50, 99, "full", 10 );		-- rest before next fight
			end
		end;


		-- If aggro, look for target
		local msg_print = false;
		player:updateBattling()
		if player.Battling then

			if( player.Current_waypoint_type == WPT_TRAVEL ) then
				cprintf(cli.green, language[113]);	-- we don't stop and don't fight back
			else
				if player:target(player:findEnemy(true,nil,evalTargetDefault)) then
					cprintf(cli.green, language[35]);	-- Got aggro. Attacking aggressive enemies.
				end
				yrest(10);
			end;
		end

		if( player.Current_waypoint_type ~= WPT_TRAVEL and player:haveTarget() ) then
			-- only fight back if it's not a TRAVEL waypoint
			-- remember players position at fight start
			local FightStartX = player.X;
			local FightStartZ = player.Z;

			-- Other check were done in defaultEvalFunction
			player:fight();

			-- check if we (as melee) can skip a waypoint because we touched it while moving to the fight place
			-- we do the check for all classes, even mostly only melees are touched by that, because only
			-- they move within the fightstart/-end
			local WPLorRPL;	-- current WP we want to reach next
			if( player.Returning ) then
				WPLorRPL = __RPL;	-- we are on a return path waypoint file
			else
				WPLorRPL = __WPL;	-- we are using a normal waypoint file
			end;
			if( WPLorRPL:getMode() ~= "wander" ) then
				local currentWp = WPLorRPL:getNextWaypoint();	-- get current wp we try to reach

				-- calculate direction in rad for: fight start postition -> current waypoint
				local dir_fightstart_to_currentwp = math.atan2(currentWp.Z - FightStartZ, currentWp.X - FightStartX);
				local dist_fightstart_to_currentwp = distance(FightStartX, FightStartZ, currentWp.X, currentWp.Z);

				-- calculate direction in rad for: fight start postition -> fight end postition
				local dir_fightstart_to_fightend = math.atan2(player.Z - FightStartZ, player.X - FightStartX);
				local dist_fightstart_to_fightend = distance(player.X, player.Z, FightStartX, FightStartZ);

				-- calculate how much  fighstart, wp and fightend are on a line, 0 = one line,
				local angleDif = angleDifference(dir_fightstart_to_currentwp, dir_fightstart_to_fightend);
				if (settings.profile.options.DEBUG_WAYPOINT) then
					printf("[DEBUG] FightStartX %s FightStartZ %s\n", FightStartX, FightStartZ );
					printf("[DEBUG] dir_FS->WP rad %.3f dir_FS->FE rad %.3f\n", dir_fightstart_to_currentwp, dir_fightstart_to_fightend );
					cprintf(cli.yellow, "[DEBUG] Line FS->WP / FS->FE: angleDif rad %.3f grad %d\n", angleDif, math.deg(angleDif) );
				end

				-- c = Wurzel (a2 + b2 - 2 a b cos (ga))
				local a = dist_fightstart_to_currentwp;
				local b = dist_fightstart_to_currentwp;
				local ga = angleDif;
				local dist_to_passed_wp = math.sqrt( math.pow(a,2) + math.pow(b,2) - 2 * a * b * math.cos(ga) );
				if (settings.profile.options.DEBUG_WAYPOINT) then
					cprintf(cli.yellow, "[DEBUG] We (would) pass(ed) wp #%s (dist %.1f) in a dist of %d (skip at %d)\n", currentWp.wpnum, dist_fightstart_to_currentwp,  dist_to_passed_wp, settings.profile.options.WAYPOINT_PASS );
				end

				if( dist_to_passed_wp < settings.profile.options.WAYPOINT_PASS  and		-- default is 100
					dist_fightstart_to_fightend >= dist_fightstart_to_currentwp ) then

					-- check position of the waypoint after the current waypoint we want to reach
					-- we don't check the closest wp, thats to much effort, we assume the distance between wp is
					-- as far, that the next one is always the closest
					local nextWp = WPLorRPL:getNextWaypoint(1);	-- get current wp we try to reach +1
					local dir_fightend_to_nextwp = math.atan2(nextWp.Z - player.Z, nextWp.X - player.X);
					local dir_fightend_to_currentwp = math.atan2(currentWp.Z - player.Z, currentWp.X - player.X );
					angleDif = angleDifference(dir_fightend_to_currentwp, dir_fightend_to_nextwp);
					if (settings.profile.options.DEBUG_WAYPOINT) then
						printf( "[DEBUG] currentWp #%s %s %s, FE->WP rad %.3f\n", currentWp.wpnum,currentWp.X,currentWp.Z,dir_fightend_to_currentwp);
						printf( "[DEBUG] nextWp #%s %s %s, FE->WP rad %.3f\n", nextWp.wpnum,nextWp.X,nextWp.Z,dir_fightend_to_nextwp);
						cprintf(cli.yellow, "[DEBUG] FE->wp#%s to FE->wp#%s is in a angle of %d grad (skip at %d)\n", nextWp.wpnum,  currentWp.wpnum, math.deg(angleDif), settings.profile.options.WAYPOINT_PASS_DEGR );
					end

					-- if next waypoint is 'in front' of current waypoint
					if( math.deg(angleDif) > settings.profile.options.WAYPOINT_PASS_DEGR ) then	-- default 90
						if (settings.profile.options.DEBUG_WAYPOINT) then
							cprintf(cli.yellow, "[DEBUG] We overrun waypoint #%d, skip it and move on to #%d\n",currentWp.wpnum, nextWp.wpnum);
						end
						cprintf(cli.green, "We overrun waypoint #%d, skip it and move on to #%d\n",currentWp.wpnum, nextWp.wpnum);
						WPLorRPL:advance();	-- set next waypoint

						-- execute the action from the skiped wp
						if( currentWp.Action and type(currentWp.Action) == "string" ) then
							local actionchunk = loadstring(currentWp.Action);
							assert( actionchunk,  sprintf(language[150], WPLorRPL.CurrentWaypoint) );
							actionchunk();
						end
						__WPL:updateResume()

					end

				end 	-- end of: check to skip a waypoint
			end


		else
		-- don't fight, move to wp
			if player.TargetPtr ~= 0 then player:clearTarget(); end

			-- First check up on eggpet
			checkEggPets()

			local wp = nil; local wpnum = nil;

			if( player.Returning ) then
				wp = __RPL:getNextWaypoint();
				wpnum = __RPL.CurrentWaypoint;
				wptag = wp.Tag~='' and ' tag: '..wp.Tag or ''
				cprintf(cli.green, language[13], wpnum, wp.X, wp.Z, wptag);	-- Moving to returnpath waypoint
			else
				wp = __WPL:getNextWaypoint();
				wpnum = __WPL.CurrentWaypoint;
				wptag = wp.Tag~='' and ' tag: '..wp.Tag or ''
				cprintf(cli.green, language[6], wpnum, wp.X, wp.Z, wptag);	-- Moving to waypoint
			end;

			player.Current_waypoint_type = wp.Type;		-- remember current waypoint type

			local success, reason = player:moveTo(wp,nil,true);

			player:updateMounted()
			if not player.Mounted then
				player:checkPotions();
				player:checkSkills( ONLY_FRIENDLY );	-- only cast hot spells to ourselfe
			end
			if( success ) then
				-- if we stick directly at a wp the counter would reseted even if we are sticked
				-- hence we reset the counter only after 3 successfull waypoints
				player.Success_waypoints = player.Success_waypoints + 1;
				if( player.Success_waypoints > 3 ) then
					player.Unstick_counter = 0;	-- reset unstick counter
				end;

				if( player.Returning ) then
					-- Completed. Return to normal waypoints.
					if( __RPL.CurrentWaypoint >= #__RPL.Waypoints ) then
						__WPL:setWaypointIndex(__WPL:getNearestWaypoint(player.X, player.Z, player.Y));
						if( __WPL.Mode == "wander" ) then
							__WPL.OrigX = player.X;
							__WPL.OrigZ = player.Z;
						end
						player.Returning = false;
						cprintf(cli.yellow, language[7]);

					else
						__RPL:advance();
					end

					-- Execute it's action, if it has one.
					if( wp.Action and type(wp.Action) == "string" and string.find(wp.Action,"%a") ) then
						keyboardRelease( settings.hotkeys.MOVE_FORWARD.key ); yrest(200) -- Stop moving
						local actionchunk = loadstring(wp.Action);
						assert( actionchunk,  sprintf(language[150], __RPL.CurrentWaypoint) );
						actionchunk();
					end
				else
					__WPL:advance();
					-- Execute it's action, if it has one.
					if( wp.Action and type(wp.Action) == "string" and string.find(wp.Action,"%a") ) then
						keyboardRelease( settings.hotkeys.MOVE_FORWARD.key ); yrest(200) -- Stop moving
						local actionchunk = loadstring(wp.Action);
						assert( actionchunk,  sprintf(language[150], __WPL.CurrentWaypoint) );
						actionchunk();
					end
				end
				__WPL:updateResume()
			else
				if( not reason == WF_TARGET ) then
					cprintf(cli.red, language[8]);		-- Waypoint movement failed
				end

				if( reason == WF_COMBAT ) then
					cprintf(cli.turquoise, language[14]);	-- We get aggro. Stop moving to waypoint
				end;

				if( reason == WF_DIST ) then
					distBreakCount = distBreakCount + 1;
				else
					if( distBreakCount > 0 ) then
						distBreakCount = 0;
					end
				end

				if reason == WF_PULLBACK and #__WPL.Waypoints > 2 then
					__WPL:setWaypointIndex(__WPL:findPulledBeforeWaypoint())
				end

				if( reason == WF_STUCK or distBreakCount > 3 ) then
					-- Get ourselves unstuck, then!
					distBreakCount = 0;
					player:clearTarget();
					player.Success_waypoints = 0;	-- counter for successfull waypoints in row
					player.Unstick_counter = player.Unstick_counter + 1;	-- count our unstick tries

					-- Too many tries, logout
					if( settings.profile.options.MAX_UNSTICK_TRIALS > 0 and
						player.Unstick_counter > settings.profile.options.MAX_UNSTICK_TRIALS ) or
						(not isInGame()) then

						if isInGame() then
							cprintf(cli.yellow, language[55],
							  player.Unstick_counter,
							  settings.profile.options.MAX_UNSTICK_TRIALS );	-- max unstick reached
						else
							cprintf(cli.yellow, language[56]);	-- The game has been disconnected
						end

						-- check if onUnstickFailure event is used in profile
						if( type(settings.profile.events.onUnstickFailure) == "function" ) and
							player.Unstick_counter > settings.profile.options.MAX_UNSTICK_TRIALS then
							pcall(settings.profile.events.onUnstickFailure);

						elseif( settings.profile.options.LOGOUT_WHEN_STUCK ) then
							if settings.profile.options.CLOSE_WHEN_STUCK == false then
								player:logout() -- doesn't close client
							else
								player:logout(nil,true); -- closes client
							end
						else
						-- pause or stop ?
							player.Sleeping = true;		-- go to sleep
							--stopPE();	-- pause the bot
							-- we should play a sound !
							player.Unstick_counter = 0;
						end
					elseif( player.Sleeping ~= true) then	-- not when to much trial and we go to sleep
						-- unstick player und unstick message
						cprintf(cli.red, language[9], player.X, player.Z, 	-- unsticking player... at position
						   player.Unstick_counter, settings.profile.options.MAX_UNSTICK_TRIALS);
						player:unstick();
					end
				end
			end

			coroutine.yield();

		end
	end

end

function resurrect()
	-- Make sure they aren't still trying to run off
	keyboardRelease(settings.hotkeys.MOVE_FORWARD.key);
	keyboardRelease(settings.hotkeys.MOVE_BACKWARD.key);
	keyboardRelease(settings.hotkeys.ROTATE_LEFT.key);
	keyboardRelease(settings.hotkeys.ROTATE_RIGHT.key);
	keyboardRelease(settings.hotkeys.STRAFF_LEFT.key);
	keyboardRelease(settings.hotkeys.STRAFF_RIGHT.key);
	player.Death_counter = player.Death_counter + 1;

	-- Take a screenshot. Only works on MicroMacro 1.0 or newer
	showWindow(getWin(), sw.show);
	yrest(500);
	local sfn = getExecutionPath() .. "/profiles/" .. player.Name .. ".bmp";
	saveScreenshot(getWin(), sfn);
	printf(language[2], sfn);

	if( type(settings.profile.events.onDeath) == "function" ) then
		local status,err = pcall(settings.profile.events.onDeath);
		if( status == false ) then
			local msg = sprintf("onDeath error: %s", err);
			error(msg);
		end
	end

	-- msg how to activate automatic resurrection
	if settings.profile.options.RES_AUTOMATIC_AFTER_DEATH ~= nil then
	settings.profile.options.RES_AFTER_DEATH = settings.profile.options.RES_AUTOMATIC_AFTER_DEATH end -- backward compatability
	if( settings.profile.options.RES_AFTER_DEATH == false ) then
		cprintf(cli.yellow, language[103]); -- If you want to use automatic resurrection
	end

	if( settings.profile.options.RES_AFTER_DEATH == true ) then
		cprintf(cli.red, language[3]);			-- Died. Resurrecting player...

		cprintf(cli.green, language[104]);  -- try to resurrect in 5 seconds
		yrest(5000);

		-- Try to revive with macro
		if( not player.Alive ) then
			cprintf(cli.green, language[107]);  -- use the ingame resurrect macro
			RoMCode("UseSelfRevive();");	-- first try self revive
			yrest(500);
			RoMCode("BrithRevive();");
			waitForLoadingScreen(30)
			yrest(settings.profile.options.WAIT_TIME_AFTER_RES);
			player:update();
		end

		-- death counter message
		cprintf(cli.green, language[109],	-- You have died %s times
		   player.Death_counter, settings.profile.options.MAX_DEATHS);

		-- check maximal death if automatic mode
		if( player.Death_counter > settings.profile.options.MAX_DEATHS ) then
			cprintf(cli.yellow, language[54], player.Death_counter,
			  settings.profile.options.MAX_DEATHS );	-- to much deaths
			player:logout();
		end

		if( player.Level > 9  and
		    player.Alive      ) then	-- no wait if resurrect at the place of death / priest / buff
			cprintf(cli.red, language[4]);		-- Returning to waypoints after 1 minute.

			-- check the first debuff that player has. (it has to be the weakness!)
			local debuff = RoMScript("GetPlayerBuffLeftTime(GetPlayerBuff(1,'HARMFUL'))");
			if(debuff == nil) then debuff = 0; end;
			debuff = tonumber(debuff);

			if (debuff == 0 and not settings.profile.options.PK_COUNTS_AS_DEATH) then
				print("This was a PK or no xp debt death.");
				player.Death_counter = player.Death_counter - 1;
			end

			player:rest(debuff,debuff+15); -- wait off the debuff before going about your path.
		end

	end

	player:updateAlive();
	-- pause if still death
	if( not player.Alive ) then
		pauseOnDeath();
	end

	-- use/compare return path if defined, if not use normal one and give a warning
	-- wen need to search the closest, hence we also accept resurrection at the death place
	player:rest(10); -- give some time to be really sure that loadscreen is gone
	-- if not it could result in loading NOT the returnpath, becaus we dont hat the new position
	player.Returning = nil;
	if( __RPL and __WPL.Mode ~= "wander" ) then
		-- compare closest waypoint with closest returnpath point
		__WPL:setWaypointIndex( __WPL:getNearestWaypoint(player.X, player.Z, player.Y ) );
		local hf_wp = __WPL:getNextWaypoint();
		local dist_to_wp = distance(player.X, player.Z, player.Y, hf_wp.X, hf_wp.Z, hf_wp.Y)

		__RPL:setWaypointIndex(__RPL:getNearestWaypoint(player.X, player.Z) );
		local hf_wp = __RPL:getNextWaypoint();
		local dist_to_rp = distance(player.X, player.Z, player.Y, hf_wp.X, hf_wp.Z, hf_wp.Y)

		if( dist_to_rp < dist_to_wp ) then	-- returnpoint is closer then next normal wayoiint
			player.Returning = true;	-- then use return path first
			cprintf(cli.yellow, language[12]);	-- Starting with return path
		end
	else
		cprintf(cli.yellow, language[111], __WPL:getFileName() ); -- don't have a defined return path
	end

	if( __RPL and __WPL.Mode == "wander" ) then
		__RPL:setWaypointIndex(1);
		player.Returning = true;
		cprintf(cli.yellow, language[12]);
	end

	-- not using returnpath, so we use the normal waypoint path
	if( player.Returning == nil) then
		player.Returning = false;
		__WPL:setWaypointIndex( __WPL:getNearestWaypoint(player.X, player.Z,player.Y ) );
		cprintf(cli.green, language[112], 	-- using normal waypoint file
		__WPL:getFileName() );
	end
end
startMacro(main,true);
