include("pawn.lua");
include("skill.lua");

WF_NONE = 0;   -- We didn't fail
WF_TARGET = 1; -- Failed waypoint because we have a target
WF_DIST = 2;   -- Broke because our distance somehow increased. It happens.
WF_STUCK = 3;  -- Failed waypoint because we are stuck on something.
WF_COMBAT = 4; -- stopped waypoint because we are in combat
WF_PULLBACK = 5; -- Failed because pulled back to before last waypoint


ONLY_FRIENDLY = true;	-- only cast friendly spells HEAL / HOT / BUFF
JUMP_FALSE = false		-- dont jump to break cast
JUMP_TRUE = true		-- jump to break cast

-- The craft numbers corespond with their order in memory
CRAFT_BLACKSMITHING = 0
CRAFT_CARPENTRY = 1
CRAFT_ARMORCRAFTING = 2
CRAFT_TAILORING = 3
CRAFT_COOKING = 4
CRAFT_ALCHEMY = 5
CRAFT_MINING = 6
CRAFT_WOODCUTTING = 7
CRAFT_HERBALISM = 8

local BreakFromFight = false
local break_fight = false;	-- flag to avoid kill counts for breaked fights
local lootIgnoreList = {}
local lootIgnoreListPos = 0

CPlayer = class(CPawn,
	function (self, ptr)
		CPawn.constructor(self) -- call pawn constructor manually without 'ptr' arg.
		self.Address = ptr;

		-- Experience tracking variables
		self.LastExpUpdateTime = os.time();
		self.LastExp = 0;				-- The amount of exp we had last check
		self.ExpUpdateInterval = 10;	-- Time in seconds to update exp
		self.ExpTable = { };			-- Holder for past exp values
		self.ExpTableMaxSize = 10;		-- How many values to track
		self.ExpInsertPos = 0;			-- Pointer to current position to overwrite (do not change)
		self.ExpPerMin = 0;				-- Calculated exp per minute
		self.TimeTillLevel = 0;			-- Time in minutes until the player will level up


		-- Directed more at player, but may be changed later.
		self.Class3 = CLASS_NONE;
		self.Level3 = 1;
		self.Pet = nil;
		self.PetPtr = 0;
		self.IgnoreTarget = 0;
		self.Battling = false; -- The actual "in combat" flag.
		self.Fighting = false; -- Internal use, does not depend on the client's battle flag
		self.Stance = 0;
		self.Nature = 0;
		self.Psi = 0;

		self.PotionLastUseTime = 0;
		self.PotionHpUsed = 0;			-- counts use of HP over time potions
		self.PotionManaUsed = 0;		-- counts use of mana over time potions
		self.PotionLastManaEmptyTime = 0;	-- timer for potion empty message
		self.PotionLastHpEmptyTime = 0;	-- timer for potion empty message

		self.PotionLastOnceUseTime = 0;
		self.PotionHpOnceUsed = 0;			-- counts use of HP potions
		self.PotionManaOnceUsed = 0;		-- counts use of mana potions
		self.PotionLastManaOnceEmptyTime = 0;	-- timer for potion empty message
		self.PotionLastHpOnceEmptyTime = 0;	-- timer for potion empty message

		self.PhiriusLastUseTime = 0;
		self.PhiriusHpUsed = 0;			-- counts use of HP phirius
		self.PhiriusManaUsed = 0;		-- counts use of mana phirius
		self.PhiriusLastManaEmptyTime = 0;	-- timer for phirius empfty message
		self.PhiriusLastHpEmptyTime = 0;	-- timer for phirius empfty message

		self.Returning = false;		-- Whether following the return path, or regular waypoints
		self.BotStartTime = os.time(); -- Records when the bot was started.
		self.BotStartTime_nr = 0;	-- Records when the bot was started, will not return at pause
		self.InventoryLastUpdate = os.time(); -- time of the last full inventory updata
		self.InventoryDoUpdate = false;	-- flag to 'force' inventory update
		self.Unstick_counter = 0;	-- counts unstick tries, resets if waypoint reached
		self.Success_waypoints = 0; -- count consecutively successfull reached waypoints
		self.Cast_to_target = 0;	-- count casts to our enemy target
		self.level_detect_levelup = 0;	-- remember player level to detect levelups
		self.Sleeping = false;		-- sleep mode with fight back if attacked
		self.Sleeping_time = 0;		-- counts the sleeping time
		self.Fights = 0;			-- counts the fights
		self.mobs = {};				-- counts the kills per target name
		self.Death_counter = 0;		-- counts deaths / automatic reanimation
		self.Current_waypoint_type = WPT_NORMAL;	-- remember current waypoint type global
		self.LastTargetPtr = 0;		-- last invalid target
		self.LastDistImprove = os.time();	-- unstick timer (dist improvement timer)
		self.FightStartTime = 0;				-- time fight started
		self.ranged_pull = false;			-- ranged pull phase active
		self.free_debug1 = 0;				-- free field for debug use
		self.free_field1 = nil;				-- free field for user use
		self.free_field2 = nil;				-- free field for user use
		self.free_field3 = nil;				-- free field for user use
		self.free_counter1 = 0;				-- free counter for user use
		self.free_counter2 = 0;				-- free counter for user use
		self.free_counter3 = 0;				-- free counter for user use
		self.free_flag1 = false;			-- free flag for user use
		self.free_flag2 = false;			-- free flag for user use
		self.free_flag3 = false;			-- free flag for user use
		self.SkillQueue = {};				-- Holds any queued skills, obviously
		self.ActualSpeed = 0
		self.Moving = false
		self.GlobalCooldown = 0
		self.LastSkill = {}
		self.failed_casts_in_a_row = 0
		self.MobIgnoreList = {}
		self.LastHitTime = 0

		if( self.Address ~= 0 and self.Address ~= nil ) then self:update(); end
	end, false -- false = do not call pawn constructor
);


function CPlayer.new()
	local playerAddress = memoryReadRepeat("uintptr", getProc(), addresses.staticbase_char, addresses.charPtr_offset);
	local np = CPlayer(playerAddress);
	np:initialize();
	np:update();
	return np;
end

function CPlayer:update()
	local addressChanged = false

	-- Ensure that our address hasn't changed. If it has, fix it.

	-- Read the address
	local tmpAddress = memoryReadRepeat("uintptr", getProc(), addresses.staticbase_char, addresses.charPtr_offset) or 0;

	-- Bad read, return
	if tmpAddress == 0 then
		return
	end

	-- Check that it's a valid address by checking the id
	local tmpId = memoryReadRepeat("uint", getProc(), tmpAddress + addresses.pawnId_offset) or 0
	if not tmpId or tmpId < PLAYERID_MIN or tmpId > PLAYERID_MAX then
		-- invalid address
		return
	end

	-- Else address good. If changed, update.
	if( tmpAddress ~= self.Address) then
		self.Address = tmpAddress;
		cprintf(cli.green, language[40], self.Address);
		addressChanged = true
		if self.Class1 == CLASS_WARDEN then
			setpetautoattacks()
		end
	end;

	local oldClass1 = self.Class1
	local oldClass2 = self.Class2
	CPawn.update(self); -- run base function
	local classChanged = self.Class1 ~= oldClass1 or self.Class2 ~= oldClass2
	local newLoad = settings.profile.skills == nil

	-- Check if we need to load the skill set.
	if next(settings.profile.skillsData) ~= nil then -- The skills are ready to be loaded
		if addressChanged or classChanged or newLoad then
			settings.loadSkillSet(self.Class1)
			if newLoad then
				-- Reset editbox false flag on start up
				if memoryReadUInt(getProc(), addresses.editBoxHasFocus_address) == 0 then
					RoMCode("z = GetKeyboardFocus(); if z then z:ClearFocus() end")
				end
			end
			if classChanged and self.TargetPtr ~= 0 then
				self:clearTarget()
			end
			addressChanged = false
		end
	end

	-- If have 2nd class, look for 3rd class
	-- Class1 and Class2 are done in the pawn class. Class3 only works for player.
	if self.Class2 ~= -1 then
		for i = 1, 8 do
			local level = memoryReadInt(getProc(),addresses.charClassInfoBase + (addresses.charClassInfoSize * i) + addresses.charClassInfoLevel_offset)
			if level > 0 and i ~= self.Class1 and i ~= self.Class2 then
				-- must be class 3
				self.Class3 = i
				break
			end
		end
	end


	self.Level = memoryReadRepeat("int", getProc(), addresses.charClassInfoBase + (addresses.charClassInfoSize* self.Class1 ) + addresses.charClassInfoLevel_offset) or self.Level
	self.Level2 = memoryReadRepeat("int", getProc(), addresses.charClassInfoBase + (addresses.charClassInfoSize* self.Class2 ) + addresses.charClassInfoLevel_offset) or self.Level2
	self.Level3 = memoryReadRepeat("int", getProc(), addresses.charClassInfoBase + (addresses.charClassInfoSize* self.Class3 ) + addresses.charClassInfoLevel_offset) or self.Level3
	self.XP = memoryReadRepeat("int", getProc(), addresses.charClassInfoBase + (addresses.charClassInfoSize* self.Class1 ) + addresses.charClassInfoXP_offset) or self.XP
	self.TP = memoryReadRepeat("int", getProc(), addresses.charClassInfoBase + (addresses.charClassInfoSize* self.Class1 ) + addresses.charClassInfoTP_offset) or self.TP

	self:updateCasting()
	self:updateBattling()
	self:updateStance() -- Also updates Stance2
	self:updateActualSpeed() -- Also updates Moving
	self:updateNature()

	if( self.Casting == nil or self.Battling == nil or self.Direction == nil ) then
		error("Error reading memory in CPlayer:update()");
	end

	self.PetPtr = memoryReadRepeat("uint", getProc(), self.Address + addresses.pawnPetPtr_offset) or self.PetPtr
	if( self.Pet == nil ) then
		self.Pet = CPawn(self.PetPtr);
	else
		self.Pet.Address = self.PetPtr;
		if( self.Pet.Address ~= 0 ) then
			self.Pet:update();
		end
	end
	self:updatePsi()
	self:updateGlobalCooldown()
end

function CPlayer:exists()
	local id = memoryReadRepeat("uint", getProc(), self.Address + addresses.pawnId_offset)
	if id and id >= PLAYERID_MIN and PLAYERID_MAX >= id then
		self.Id = id
		return true
	else
		return false
	end
end

function CPlayer:checkAddress()
	local tmpAddress = memoryReadRepeat("uintptr", getProc(), addresses.staticbase_char, addresses.charPtr_offset) or 0;
	if( tmpAddress ~= self.Address and tmpAddress ~= 0 ) then
		self:update()
	end
end

function CPlayer:updateCasting()
	self.Casting = (memoryReadRepeat("intptr", getProc(), addresses.castingBarPtr, addresses.castingBar_offset) ~= 0);
end

function CPlayer:updateBattling()
	self.Battling = memoryReadRepeat("byteptr", getProc(), addresses.staticbase_char, addresses.charBattle_offset) == 1;

	-- remember aggro start time, used for timed ranged pull
	if( self.Battling == true ) then
		if(self.aggro_start_time == 0) then
			self.aggro_start_time = os.time();
		end
	else
		self.aggro_start_time = 0;
	end
end

function CPlayer:updateStance()
	self.Stance = memoryReadRepeat("byteptr", getProc(), addresses.staticbase_char, addresses.charStance_offset) or self.Stance
	self.Stance2 = memoryReadRepeat("byteptr", getProc(), addresses.staticbase_char, addresses.charStance_offset + 2) or self.Stance2
end

function CPlayer:updateActualSpeed()
	self.ActualSpeed = memoryReadFloatPtr(getProc(), addresses.staticbase_char, addresses.actualSpeed_offset) or self.ActualSpeed
	self.Moving = (self.ActualSpeed > 0)
end

function CPlayer:updateNature()
	local tmp = self:getBuff(503827)
	if tmp then -- has natures power
		self.Nature = tmp.Level + 1
	else
		self.Nature = 0
	end
end

function CPlayer:updatePsi()
	self.Psi = memoryReadRepeat("uint",getProc(), addresses.psi)
end

function CPlayer:updateGlobalCooldown()
	self.GlobalCooldown = memoryReadRepeat("int", getProc(), addresses.staticCooldownsBase)/10
end

-- Inserts a skill at the end of the queue.
-- Accepts a skill name like PRIEST_RISING_TIDE
-- or a skill object
function CPlayer:queueSkill(skill, blocking)
	blocking = blocking or false;

	if( type(skill) == "string" ) then
		local skill_found = false;
		for i,v in pairs(settings.profile.skills) do
			if( v.Name == skill ) then
				skill_found = true;
				skill = CSkill(v);
				break;
			end
		end

		if( skill_found == false ) then
			cprintf(cli.yellow, "Unknown profile skill %s. Check your manual castings "..
			  "(e.g. in the events or waypoint files). Be sure the skill is in the "..
			  "skills section of your profile.\n", skill);
			return;
		end
	end

	if( type(skill) ~= "table" ) then
		cprintf(cli.red, "[DEBUG] Error: 'skill' is not a table in CPlayer:queueSkill()\n");
		return;
	end

	skill.Blocking = blocking;
	table.insert(self.SkillQueue, skill);
end

function CPlayer:flushSkillQueue()
	self.SkillQueue = {};
end

function CPlayer:getNextQueuedSkill()
	if( #self.SkillQueue > 0 ) then
		return self.SkillQueue[1];
	else
		return nil;
	end
end

function CPlayer:popSkillQueue()
	table.remove(self.SkillQueue, 1);
end

function CPlayer:harvest(_id)
	local function findNearestHarvestable(_id, ignore)
		self:updateXYZ()
		ignore = ignore or 0;
		local closestHarvestable = nil;
		local obj = nil;
		local objectList = CObjectList();
		objectList:update();

		for i = 0,objectList:size() do
			obj = objectList:getObject(i);

			if( obj ~= nil ) then
				if( obj.Type == PT_NODE and obj.Address ~= ignore and
					(_id == obj.Id or (not _id and database.nodes[obj.Id])) ) then
					local harvestThis = true;
					if( database.nodes[obj.Id] ) then
						local harvestType = database.nodes[obj.Id].Type;
						if( harvestType == NTYPE_WOOD and settings.profile.options.HARVEST_WOOD == false ) then
							harvestThis = false;
						elseif( harvestType == NTYPE_HERB and settings.profile.options.HARVEST_HERB == false ) then
							harvestThis = false;
						elseif( harvestType == NTYPE_ORE and settings.profile.options.HARVEST_ORE == false ) then
							harvestThis = false;
						end
					end

					if( harvestThis == true ) then
						if( closestHarvestable == nil ) then
							if( distance(self.X, self.Z, self.Y, obj.X, obj.Z, obj.Y) < settings.profile.options.HARVEST_DISTANCE ) then
								closestHarvestable = obj;
							end
						else
							if( distance(self.X, self.Z, self.Y, obj.X, obj.Z, obj.Y) <
								distance(self.X, self.Z, self.Y, closestHarvestable.X, closestHarvestable.Z, closestHarvestable.Y) ) then
								-- this node is closer
								closestHarvestable = obj;
							end
						end
					end
				end
			end
		end

		return closestHarvestable;
	end

	local function nodeStillFound(node)
		local closestHarvestable = nil;
		local obj = nil;
		local objectList = CObjectList();
		objectList:update();

		for i = 0,objectList:size() do
			obj = objectList:getObject(i);
			if( obj.Address == node.Address ) then
				return true;
			end
		end

		return false;
	end

	-- Make sure we come to a stop before attempting to harvest.
	self:waitTillStopMoving()

	local lastHarvestedNodeAddr = nil;
	while(true) do
		closestHarvestable = findNearestHarvestable(_id, lastHarvestedNodeAddr);
		if( closestHarvestable == nil ) then
			printf(language[79]);
			return;
		end

		if( type(settings.profile.events.onHarvest) == "function" ) then
			arg1 = CPawn(closestHarvestable.Address);
			local status,result = pcall(settings.profile.events.onHarvest);
			if( status == false ) then
				local msg = sprintf("onHarvest error: %s", result);
				error(msg);
			end

			if( result == false ) then -- They chose to not harvest this in the event
				return;
			end
		end

		-- Check harvest skill level.
		if database.nodes[closestHarvestable.Id] then
			local harvestLevel = database.nodes[closestHarvestable.Id].Level
			local harvestType = database.nodes[closestHarvestable.Id].Type
			local craftLevel
			if harvestType == NTYPE_ORE then
				craftLevel = self:getCraftLevel(CRAFT_MINING)
			elseif harvestType == NTYPE_WOOD then
				craftLevel = self:getCraftLevel(CRAFT_WOODCUTTING)
			elseif harvestType == NTYPE_HERB then
				craftLevel = self:getCraftLevel(CRAFT_HERBALISM)
			end
			if harvestLevel > craftLevel then
				print(language[76]) -- Harvest skill level too low
				return false;
			end
		end

		cprintf(cli.yellow, language[95], closestHarvestable.Name);

		if( distance(self.X, self.Z, self.Y, closestHarvestable.X, closestHarvestable.Z, closestHarvestable.Y) > 80 ) then
			self:moveInRange(CWaypoint(closestHarvestable.X, closestHarvestable.Z), 39, true);
		end

		if( nodeStillFound(closestHarvestable) ) then
			self:target(closestHarvestable.Address)
			yrest(100)
			Attack();
			yrest(100);
		else
			return; -- Node disappeared for whatever reason...
		end

		if _id and not database.nodes[closestHarvestable.Id] then -- The rest is not needed if not resource node
			return true;
		end

		yrest(500);

		self:updateBattling();
		self:updateHarvesting();
		local timeStart = getTime();
		local skip = false;
		while( not self.Harvesting ) do
			-- Wait to start harvesting
			yrest(100);
			self:updateBattling();
			self:updateHarvesting();
			if( self.Battling ) and self:findEnemy(true,nil,evalTargetDefault) then
				printf(language[78]);
				skip = true;
				break;
			end

			if( deltaTime(getTime(), timeStart) > 3000 ) then
				-- Maybe the command didn't go through. Try once more.
				Attack()
				yrest(500);
				break;
			end
		end
		self:updateBattling();
		self:updateHarvesting();
		timeStart = getTime();
		while( self.Harvesting and skip == false ) do
			yrest(100);
			self:updateBattling();
			self:updateHarvesting();
			if( self.Battling and self:findEnemy(true,nil,evalTargetDefault) ) then
				printf(language[78]);
				break;
			end

			if( not nodeStillFound(closestHarvestable) or self.TargetPtr ~= closestHarvestable.Address ) then
				break;
			end

			if( deltaTime(getTime(), timeStart) > 45000 ) then
				-- Taking too long. Drop out.
				printf("Stop harvesting. Taking too long.\n");
				break;
			end
		end

		self:updateBattling();
		local interrupted = false
		while( self.Battling ) do
			if self:target(self:findEnemy(true,nil,evalTargetDefault)) then
				interrupted = true
				self:fight();
			else
				break
			end
		end

		if not interrupted then
			lastHarvestedNodeAddr = closestHarvestable.Address;
		end
	end
end

-- Returns nil if nothing found, otherwise returns a pawn
function CPlayer:findEnemy(aggroOnly, _id, evalFunc, ignore)
	-- If aggroonly, check to see if you have already started attacking current target
	if aggroOnly then
		if self:haveTarget() then
			local target = CPawn.new(self.TargetPtr)
			target:updateLastHP()
			if target.TargetPtr == self.Address or
			target:targetIsFriend() or
			target.LastHP > 0 then
				return target
			end
		end
	end

	-- Otherwise look for target
	self:updateXYZ()
	ignore = ignore or 0;
	local aggroOnly = aggroOnly or false;
	local bestEnemy = nil;
	local bestScore = 0;
	local obj = nil;
	local objectList = CObjectList();
	objectList:update();

	if( type(evalFunc) ~= "function" ) then
		evalFunc = function (unused) return true; end;
	end

	-- The 'max' values that each scoring sub-part uses
	local SCORE_DISTANCE = 60;      -- closer = more score; actually score will usually be less than half
	local SCORE_AGGRESSIVE = 80;    -- aggressive = score
	local SCORE_ATTACKING = 200;    -- attacking = score
	local SCORE_HEALTHPERCENT = 75; -- lower health = more score

	for i = 0,objectList:size() do
		obj = objectList:getObject(i);
		if( obj ~= nil ) then
			local inp = memoryReadRepeat("int", getProc(), obj.Address + addresses.pawnAttackable_offset)
			if inp and not bitAnd(inp,0x4000000) then -- Invisible/attackable?
				if( obj.Type == PT_MONSTER and (_id == obj.Id or _id == nil) and obj.Address ~= ignore) then
					local dist = distance(self.X, self.Z, obj.X, obj.Z)
					if dist < settings.profile.options.MAX_TARGET_DIST then

						local pawn = CPawn.new(obj.Address);
						pawn:updateTargetPtr()

						if( evalFunc(pawn.Address, pawn) == true ) then
							pawn:updateXYZ()
							pawn:updateInParty()
							if ((pawn.TargetPtr == self.Address or pawn:targetIsFriend()) and
							aggroOnly == true) or aggroOnly == false then
								local currentScore = 0;
								pawn:updateHP()
								currentScore = currentScore + ( (settings.profile.options.MAX_TARGET_DIST - dist) / settings.profile.options.MAX_TARGET_DIST * SCORE_DISTANCE );
								currentScore = currentScore + ( (pawn.MaxHP - pawn.HP) / pawn.MaxHP * SCORE_HEALTHPERCENT );
								if( pawn.TargetPtr == self.Address or pawn:targetIsFriend() ) then currentScore = currentScore + SCORE_ATTACKING; end;
								if( pawn.Aggressive ) then
									currentScore = currentScore + SCORE_AGGRESSIVE;
								end;
								if( bestEnemy == nil ) then
									bestEnemy = obj;
									bestScore = currentScore;
								elseif( currentScore > bestScore ) then
									bestEnemy = obj;
									bestScore = currentScore;
								end
							end
						end
					end
				end
			end
		end
	end

	if( bestEnemy ) then
		return CPawn(bestEnemy.Address);
	else
		return nil;
	end
end

function CPlayer:target(pawnOrAddress)
	local address = nil;
	if( type(pawnOrAddress) == "number" ) then
		address = pawnOrAddress;
	elseif( type(pawnOrAddress) == "table" ) then
		address = pawnOrAddress.Address;
	end

	if( address == nil ) then return false; end;

	local addressId = memoryReadRepeat("uint", getProc(), address + addresses.pawnId_offset) or 0;

	if addressId == 0 or addressId > 999999 then -- The pawn or object no longer exists
		self.TargetPtr = 0
		return false
	end

	local flags = memoryReadUInt(getProc(),address + addresses.pawnAttackable_offset)
	if bitAnd(flags,0x10) and settings.options.TARGET_FRAME then -- Has bloodbar
		local guid = memoryReadUInt(getProc(), address + addresses.pawnGUID_offset)
		RoMCode("OBB_ChangeTraget("..tostring(guid)..")")
	end
	memoryWriteInt(getProc(), self.Address + addresses.pawnTargetPtr_offset, address);
	self.TargetPtr = address

	return true
end

function CPlayer:initialize()
	memoryWriteInt(getProc(), self.Address + addresses.pawnCasting_offset, 0);
end

-- Resets "toggled" combat skills to off & used counter to 0
function CPlayer:resetSkills()
	for i,v in pairs(settings.profile.skills) do
		if v.Toggled and (v.Type == STYPE_DAMAGE or v.Type ==STYPE_DOT) then
			v.Toggled = false;
		end
		if( v.used ) then
			v.used = 0;
		end
	end
end

-- Resets skill cooldowns / used after death
function CPlayer:resetSkillLastCastTime()
	for i,v in pairs(settings.profile.skills) do
		if( v.Name ~= "PRIEST_SOUL_BOND" ) then		-- real cooldown of 1800 sec

			v.LastCastTime = { low = 0, high = 0 };
		end;
	end
end

local function RestWhileCheckingForWaypoint(_duration)
	player:updateActualSpeed()
	if #__WPL.Waypoints > 0 and player.Moving and not player.Fighting then
		-- rest for _duration but if moving stop when reaching waypoint
		local starttime = os.clock()
		local curWP = __WPL.Waypoints[__WPL.CurrentWaypoint]
		local lastdist = distance(player.X,player.Z,curWP.X, curWP.Z)
		repeat
			startdist = lastdist
			yrest(10)
			player:updateXYZ()
			lastdist = distance(player.X, player.Z, curWP.X, curWP.Z)
			if (lastdist < 10 or lastdist > startdist) then -- and wp reached or moving away
				return false
			end
		until (os.clock() - starttime) > _duration/1000
	else
		yrest(_duration)
	end
	return true
end

-- Make sure you face the target
local function faceTarget()

	if player.Cast_to_target == 0 or
		player.LastSkill.CastTime == 0 or
		player.LastSkill.ClickToCast ~= true then

		player:updateTargetPtr()
		local target = CPawn.new(player.TargetPtr)
		target:updateType()
		if not target:exists() or target.Type ~= PT_MONSTER then return end

		target:updateXYZ()
		if( settings.profile.options.QUICK_TURN ) then
			local angle = math.atan2(target.Z - player.Z, target.X - player.X);
			local yangle = math.atan2(target.Y - player.Y, ((target.X - player.X)^2 + (target.Z - player.Z)^2)^.5 );
			player:faceDirection(angle, yangle);

			camera:setRotation(angle);
		elseif( settings.options.ENABLE_FIGHT_SLOW_TURN ) then
			-- Make sure we're facing the enemy
			local angle = math.atan2(target.Z - player.Z, target.X - player.X);
			local yangle = math.atan2(target.Y - player.Y, ((target.X - player.X)^2 + (target.Z - player.Z)^2)^.5 );
			player:faceDirection(player.Direction, yangle); -- change only 'Y' angle with 'faceDirection'.
			local angleDif = angleDifference(angle, player.Direction);
			local correctingAngle = false;
			local startTime = os.time();

			while( angleDif > math.rad(15) ) do
				if( player.HP < 1 or player.Alive == false ) then
					player.Fighting = false;
					return;
				end;

				if( os.difftime(os.time(), startTime) > 5 ) then
					printf(language[26]);
					break;
				end;

				correctingAngle = true;
				if( angleDifference(angle, player.Direction + 0.01) < angleDif ) then
					-- rotate left
					keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
					keyboardHold( settings.hotkeys.ROTATE_LEFT.key );
				else
					-- rotate right
					keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
					keyboardHold( settings.hotkeys.ROTATE_RIGHT.key );
				end

				yrest(100);
				player:updateDirection();
				target:updateXYZ();
				angle = math.atan2(target.Z - player.Z, target.X - player.X);
				angleDif = angleDifference(angle, player.Direction);
			end

			if( correctingAngle ) then
				keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
				keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
			end

		end
	end
end

local function mobsInRangeOfLastClickToCast()
	local obj = nil;
	local objectList = CObjectList();
	objectList:update();
	for i = 0,objectList:size() do
		obj = objectList:getObject(i);
		if obj.Type == PT_MONSTER then
			local dist = distance(player.LastSkill.AimedAt, obj)
			if dist < player.LastSkill.AOERange then -- in range
				local pawn = CPawn.new(obj.Address);
				pawn:updateAlive()
				if pawn.Alive then
					-- Mob alive in range.
					return true
				end
			end
		end
	end
end

function CPlayer:cast(skill)
	local last_globalcooldown
	-- Waits till casting or GCD ends minus SKILL_USE_PRIOR. Assumes if both then GDC follows casting
	local function waitTillCastingEnds()
		local prior = getSkillUsePrior();
		self:updateCasting()
		while(self.Casting) do
			-- break cast with jump if aggro before casting finished
			if( self:check_aggro_before_cast(JUMP_TRUE, self.LastSkill.Type)) then	--  with jump
				printf(language[82]);	-- close print 'Casting ..."
				return;
			end;

			-- break if target is dead or mobs moved out of range ot AOE.
			if( self.LastSkill.Type == STYPE_DAMAGE or
				self.LastSkill.Type == STYPE_DOT ) then
				-- Check if there are still mobs alive within range
				if self.LastSkill and self.LastSkill.ClickToCast then
					if not mobsInRangeOfLastClickToCast() then
						-- if no mobs in range of clicktocast then break cast so it can cast the next skill
						keyboardPress(settings.hotkeys.MOVE_BACKWARD.key)
						if not self:haveTarget() then
							return
						end
					end
				elseif not self:haveTarget() then
					return
				end
			end

			-- Waiting for casting to finish...
			yrest(10);
			self:updateCasting();

			faceTarget()

			-- leave before Casting flag is gone, so we can cast faster, but only if skill doesn't trigger global cooldown and ClickToCast ~= true
			if self.LastSkill.GlobalCooldown ~= true and skill.ClickToCast ~= true then
				if self:getRemainingCastTime() <= prior/1000 then
					-- end of waiting early
					break;
				end
			end
		end
		if skill.Cooldown > 0 then
			while skill:getRemainingCooldown() > prior/1000 do
				-- Waiting for cooldown to finish...
				yrest(10)
			end
		end
		if (self.LastSkill.CastTime and self.LastSkill.CastTime > 0) or
				(self.LastSkill.Mana == 0 and self.LastSkill.Rage == 0 and self.LastSkill.Energy == 0 and self.LastSkill.Focus == 0) then

			self:updateGlobalCooldown()
			while self.GlobalCooldown > prior/1000 do
				-- Waiting for global cooldown to finish...
				yrest(10)
				self:updateGlobalCooldown();
			end
		end
	end

	-- If during last moments of cast or GCD, wait will ends
	local function waitTillPriorEnds()
		local prior = getSkillUsePrior();
		self:updateCasting()
		self:updateGlobalCooldown()
		if self.Casting and self:getRemainingCastTime() <= prior/1000 then
			repeat
				yrest(10)
				self:updateCasting()
			until not self.Casting or self:getRemainingCastTime() > prior/1000
		elseif skill.Cooldown > 0 and skill:getRemainingCooldown() <= prior/1000 then
			local remainingCooldown
			repeat
				yrest(10)
				remainingCooldown = skill:getRemainingCooldown()
			until remainingCooldown == 0 or remainingCooldown > prior/1000
		elseif self.GlobalCooldown > 0 and self.GlobalCooldown < last_globalcooldown then
			-- Wait until global cooldown is 0 or jumps to the next skill
			repeat
				yrest(10)
				self:updateGlobalCooldown();
			until self.GlobalCooldown == 0 or self.GlobalCooldown > last_globalcooldown
		end
	end

	-- Wait for casting or GCD to start
	local function waitTillCastingStarts()
		local startTime = getTime();
		self:updateCasting();
		if( skill.CastTime > 0 ) then
			while( not self.Casting ) do -- wait for casting to start
				-- Check if mob is dead during wait, only for damage skills
				if skill.Type ~= STYPE_HEAL and skill.Type ~= STYPE_HOT then
					local target = CPawn.new(self.TargetPtr);
					if not target:isAlive() then
						printf(language[82]);	-- close print 'Casting ..." / aborted
						return false
					end
				end

				-- break cast with jump if aggro before casting finished
				if self:check_aggro_before_cast(JUMP_TRUE, skill.Type) then	-- with jump
					printf(language[82]);	-- close print 'Casting ..." / aborted
					return false
				end;

				yrest(50);
				self:updateCasting();
				if( deltaTime(getTime(), startTime) > 1500 ) then -- Assume failed to caste after 1.5 sec
					printf(language[180]);	-- close print 'Casting ..." / aborted
					if skill.Type == STYPE_DAMAGE or skill.Type == STYPE_DOT then
						self.failed_casts_in_a_row = self.failed_casts_in_a_row + 1
					end
					return false
				end
			end;
			skill.LastCastTime = getTime()
			local left,casttime = self:getRemainingCastTime()
			skill.LastCastTime.low = skill.LastCastTime.low + casttime*1000 * bot.GetTimeFrequency;
		elseif skill.Cooldown > 0 then
			local startTime = getTime()
			while skill:getRemainingCooldown() == 0 do
				-- Check if mob is dead during wait
				local target = CPawn.new(self.TargetPtr);
				if not target:isAlive() and deltaTime(getTime(),startTime) > 500 then
					printf(language[82]);	-- close print 'Casting ..." / aborted
					return false
				end

				if not skill.Toggleable then
					if(skill.hotkey == "MACRO" or skill.hotkey == "" or skill.hotkey == nil ) then
						SlashCommand("/script CastSpellByName(\""..GetIdName(skill.Id).."\");");
					else
						keyboardPress(skill.hotkey, skill.modifier);
					end
				end
				if RestWhileCheckingForWaypoint(300) == false then break end -- break to head to new wp
				if( deltaTime(getTime(), startTime) > 1500 ) then -- Assume failed to caste after .7 sec
					printf(language[180]);	-- close print 'Casting ..." / aborted
					if skill.Type == STYPE_DAMAGE or skill.Type == STYPE_DOT then
						self.failed_casts_in_a_row = self.failed_casts_in_a_row + 1
					end
					return false
				end
			end;
			skill.LastCastTime = getTime()
			local remaining = skill:getRemainingCooldown()
			if remaining > 0 then
				skill.LastCastTime.low = skill.LastCastTime.low +remaining*1000 * bot.GetTimeFrequency
			end
		elseif skill.GlobalCooldown ~= false then -- Wait for global cooldown to start
			self:updateGlobalCooldown()
			local startTime = getTime()
			while self.GlobalCooldown == 0 do -- wait for casting to start
				-- Check if mob is dead during wait
				local target = CPawn.new(self.TargetPtr);
				if not target:isAlive() and deltaTime(getTime(),startTime) > 500 then
					printf(language[82]);	-- close print 'Casting ..." / aborted
					return false
				end

				if RestWhileCheckingForWaypoint(50) == false then break end -- break to head to new wp
				self:updateGlobalCooldown();
				if( deltaTime(getTime(), startTime) > 1000 ) then -- Assume failed to caste after .7 sec
					printf(language[180]);	-- close print 'Casting ..." / aborted
					if skill.Type == STYPE_DAMAGE or skill.Type == STYPE_DOT then
						self.failed_casts_in_a_row = self.failed_casts_in_a_row + 1
					end
					return false
				end
			end;
			skill.LastCastTime = getTime()
		end
		player.LastSkill.LastCastTime = table.copy(skill.LastCastTime)
		if skill.Type == STYPE_DAMAGE or skill.Type == STYPE_DOT then
			self.failed_casts_in_a_row = 0
		end
	end

	-- If given a string, look it up.
	-- If given a skill object, use it natively.
	if( type(skill) == "string" ) then
		local skill_found = false;
		for i,v in pairs(settings.profile.skills) do
			if( v.Name == skill ) then
				skill_found = true;
				skill = v; break;
			end
		end
		if( skill_found == false ) then
			cprintf(cli.yellow, "Unknown profile skill %s. Check your manual castings "..
			  "(e.g. in the events or waypoint files). Be sure the skill is in the "..
			  "skills section of your profile.\n", skill);
		end
	end

	-- Still on cooldown
	if skill.Cooldown > 0 and skill:getRemainingCooldown() > 1 then
		return
	end

	local hf_temp;
	if( skill.hotkey == "MACRO" or skill.hotkey == "" or skill.hotkey == nil) then
		hf_temp = "MACRO";
	else
		hf_temp = getKeyName(skill.hotkey);
	end

	local continue = true;
	if( type(settings.profile.events.onPreSkillCast) == "function" ) then
		arg1 = skill;
		if ( onPreSkillCast_active ~= true ) then	-- avoid calling event if already within an event
			onPreSkillCast_active = true;
			local status,result = pcall(settings.profile.events.onPreSkillCast);
			if( status == false ) then
				local msg = sprintf("onPreSkillCast error: %s", result);
				error(msg);
			end
			if( result == false ) then continue = false; end;
			onPreSkillCast_active = false;
		end
	end

	if(continue == true) then
		-- Wait for previous cast or GCD to end minus undercut.
		waitTillCastingEnds()

		-- break if target is dead
		if( skill.Type == STYPE_DAMAGE or
			skill.Type == STYPE_DOT ) and
			not self:haveTarget() and skill.AOECenter ~= SAOE_PLAYER then
			return;
		end

		-- fixes instant cast after timed cast timing problem, waits till finished
		while skill.CastTime == 0 and self.Casting do
			self:updateCasting(); yrest(10);
		end

		-- first part of 'casting ...'
		printf(language[21], hf_temp, string.sub(skill.Name.."                      ", 1, 20));

		faceTarget()

		self:updateGlobalCooldown()
		last_globalcooldown = self.GlobalCooldown
		self:updateMP()
		local oldMana, oldRage, oldEnergy, oldFocus = self.Mana, self.Rage, self.Energy, self.Focus
		skill:use();

		-- count attempted cast to enemy targets
		if( skill.Target == STARGET_ENEMY ) then	-- target is unfriendly
			self.Cast_to_target = self.Cast_to_target + 1;
		end;

		-- Bypass the rest as Warden Summon is alread completed
		if self.Class1 == CLASS_WARDEN and skill.Type == STYPE_SUMMON then
			return
		end

		-- wait for previous cast or GCD to finish if last moments of casting.
		waitTillPriorEnds()

		-- Wait for casting or GCD to start
		if waitTillCastingStarts() == false then -- failed to cast/aborted
			return "failed to cast"
		end

		if( skill.Toggleable ) then
			skill.Toggled = true;
		end

		-- Wait until energy use in memory updates. Only for instants
		if skill.CastTime == 0 then
			if skill.GlobalCooldown ~= false then
				local mpstart = getTime()
				repeat
					yrest(10)
					player:updateMP()
					if skill.Mana > 0 then
						if self.Mana < oldMana then break end
					elseif skill.Rage > 0 then
						if self.Rage < oldRage then break end
					elseif skill.Energy > 0 then
						if self.Energy < oldEnergy then break end
					elseif skill.Focus > 0 then
						if self.Focus < oldFocus then break end
					else
						break
					end
				until deltaTime(getTime(),mpstart) > 700
			end
		end

		-- print HP of our target
		-- we do it later, because the client needs some time to change the values
		local target = CPawn.new(self.TargetPtr);
		if target:exists() then
			target:updateName()
			target:updateHP()
		end

		target:updateHP()
		printf("=>   "..target.Name.." ("..target.HP.."/"..target.MaxHP..")\n");	-- second part of 'casting ...'

		if( type(settings.profile.events.onSkillCast) == "function" ) then
			arg1 = skill;
			if ( onSkillCast_active ~= true ) then	-- avoid calling event if already within an event
				onSkillCast_active = true;
				local status,err = pcall(settings.profile.events.onSkillCast);
				if( status == false ) then
					local msg = sprintf("onSkillCast error: %s", err);
					error(msg);
				end
				onSkillCast_active = false;
			end
		end
	else
		continue = true;
	end

end

-- Check if you can use any skills, and use them
-- if they are needed.
function CPlayer:checkSkills(_only_friendly, target)

	local function takingTooLongToDamageTarget(target)
		self:updateCasting()
		if ( target ~= nil and target:exists() and _only_friendly ~= true ) then
			target:updateLastHP()
		end

		if( self.Cast_to_target >= settings.profile.options.MAX_SKILLUSE_NODMG  and	(target.LastHP == 0 or
			self.failed_casts_in_a_row >= settings.profile.options.MAX_SKILLUSE_NODMG) and not self.Casting) then
			printf(1 ..language[83]);			-- Taking too long to damage target
			self:addToMobIgnoreList(target.Address)
			self:clearTarget();

			self:updateBattling()
			if( self.Battling ) then
				yrest(1000);
				keyboardHold( settings.hotkeys.MOVE_BACKWARD.key);
				yrest(1000);
				keyboardRelease( settings.hotkeys.MOVE_BACKWARD.key);
				yrest(1000)
				self:updateXYZ();
			end

			break_fight = true;
			return true
		end

		return false
	end

	local function checkSystemMessages(target)
		if COLLISION_MSG == nil then COLLISION_MSG = getTEXT("SYS_CASTSPELL_TARGET_COLLISION") end
		if FACETARGET_MSG == nil then FACETARGET_MSG = getTEXT("SYS_GAMEMSGEVENT_106") end

		local lastTime = getLastWarning(COLLISION_MSG,2)
		if lastTime and lastTime ~= checkskills_last_collision then
			checkskills_last_collision = lastTime
			self:updateBattling()
			if not self.Battling then
				player:addToMobIgnoreList(target)
			else
				keyboardHold(settings.hotkeys.MOVE_BACKWARD.key)
				yrest(1000);
				keyboardRelease( settings.hotkeys.MOVE_BACKWARD.key);
				self:updateXYZ();
			end
			self:clearTarget()
		else
			lastTime = getLastWarning(FACETARGET_MSG,2)
			if lastTime and lastTime ~= checkskills_last_facetarget then
				checkskills_last_facetarget = lastTime
				target:updateXYZ()
				player:updateXYZ()
				local angle = math.atan2(target.Z - player.Z, target.X - player.X);
				local angleDif = angleDifference(angle, player.Direction);
				if angleDif > math.rad(90) then -- Behind you. Turn around.
					faceTarget()
				else -- Already in front of you. Take some steps back.
					keyboardHold( settings.hotkeys.MOVE_BACKWARD.key);
					yrest(1000);
					keyboardRelease( settings.hotkeys.MOVE_BACKWARD.key);
					self:updateXYZ();
				end
			end
		end
	end

	local used = false;
	--if settings.profile.options.DISMOUNT == false and player.Mounted then return false end

	player:updateTargetPtr()
	local target = target or CPawn.new(self.TargetPtr);

	local useQueue = false; -- Whether to use the regular profile skills
	if( #self.SkillQueue > 0 ) then
		-- Queue is not empty. See if we can cast anything
		local skill = self:getNextQueuedSkill();
		if not takingTooLongToDamageTarget(target) then
			if( skill:canUse(false, target) ) then
				if( skill.CastTime > 0 ) then
					keyboardRelease( settings.hotkeys.MOVE_FORWARD.key );
					 -- Wait to stop only if not an instant cast spell
					self:waitTillStopMoving()
				end

				if self:cast(skill) == "failed to cast" then
					checkSystemMessages(target)
				else
					used = true;
					self:popSkillQueue();
				end
			else
				if( skill.Blocking ) then
					useQueue = true;
				else
					self:popSkillQueue();
				end
			end
		end
	else
		-- Queue is empty, continue like normal
		useQueue = true;
	end

	if useQueue or #self.SkillQueue == 0 then
		local last_dist_to_wp
		local attack_skill_used = false -- Used for priority casting
		for i,v in pairs(settings.profile.skills) do
			if( v.AutoUse and v:canUse(_only_friendly, target) ) and
			  (settings.profile.options.PRIORITY_CASTING ~= true or attack_skill_used == false or (v.Type ~= STYPE_DAMAGE and v.Type ~= STYPE_DOT)) then
				self:updateActualSpeed()
				self:updateXYZ()
				-- break if just checking buff, moving and reached WP. So it can turn
				if _only_friendly and #__WPL.Waypoints > 0 and self.Moving then
					local curWP = __WPL.Waypoints[__WPL.CurrentWaypoint]
					local distToWP = distance(self.X, self.Z, curWP.X, curWP.Z)
					if distToWP < 10 then -- wp reached
						break
					end
					if last_dist_to_wp and distToWP > last_dist_to_wp then -- moving away from wp
						break
					end

					last_dist_to_wp = distToWP
				end

				-- additional potion check while working at a 'casting round'
				self:checkPotions();

				-- Short time break target: after x casts without damaging
				self:updateTargetPtr()
				local target = CPawn.new(self.TargetPtr)

				if takingTooLongToDamageTarget(target) then
					break
				end

				if settings.profile.options.PRIORITY_CASTING == true and (v.Type == STYPE_DAMAGE or v.Type == STYPE_DOT) then
					attack_skill_used = true
				end

				if self:cast(v) == "failed to cast" then
					checkSystemMessages(target)
				else
					used = true;
				end
			end
		end
	else
		self:checkPotions();
	end

	return used;
end

-- Check if you need to use any potions, and use them.
function CPlayer:checkPotions()
-- only one potion type could be used, so we return after using one type

	--=== If rogue is hidden then don't use potions as it breaks hide ===--
	if self.Class1 == 3 and self:hasBuff(500675) then return false end

	self:updateHP()
	self:updateMP()
	self:updateCasting()

	if settings.profile.options.USE_PHIRIUS_POTION == true then
		-- If we need to use a health potion
		if( (self.HP/self.MaxHP*100) < settings.profile.options.PHIRIUS_HP_LOW  and
			os.difftime(os.time(), self.PhiriusLastUseTime) > 19 )  then
			item = inventory:bestAvailablePhirius("phirushealing");
			if( item ) then
				if self.Casting then self:waitTillCastingEnds() end -- wait if still casting minus undercut
				local checkItemName = item.Name;
				item:update();
				if( checkItemName ~= item.Name ) then
					cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
					item:update();
				else
					item:use();
					self.PhiriusHpUsed = self.PhiriusHpUsed + 1;			-- counts use of HP potions

					cprintf(cli.green, language[10], 		-- Using HP potion
					   self.HP, self.MaxHP, self.HP/self.MaxHP*100,
					   item.Name, item.ItemCount);
					   RestWhileCheckingForWaypoint(1000)
					if( self.Fighting ) then
						yrest(1000);
					end

					self.PhiriusLastUseTime = os.time();
				end

				return true;
			else		-- potions empty
				if( os.difftime(os.time(), self.PhiriusLastHpEmptyTime) > 16 ) then
					cprintf(cli.yellow, "No more usable HP Phirius pots\n");
					self.PhiriusLastHpEmptyTime = os.time();
					-- full inventory update if potions empty
					if( os.difftime(os.time(), self.InventoryLastUpdate) >
					  settings.profile.options.INV_UPDATE_INTERVAL ) then
						self.InventoryDoUpdate = true;
					end
				end;
			end
		end

		-- If we need to use a mana potion(if we even have mana)
		if( self.MaxMana > 0 ) then
			if( (self.Mana/self.MaxMana*100) < settings.profile.options.PHIRIUS_MP_LOW  and
				os.difftime(os.time(), self.PhiriusLastUseTime) > 19 )  then
				item = inventory:bestAvailablePhirius("phirusmana");
				if( item ) then
					if self.Casting then self:waitTillCastingEnds() end -- wait if still casting minus undercut
					local checkItemName = item.Name;
					item:update();
					if( checkItemName ~= item.Name ) then
						cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
						item:update();
					else
						item:use();
						self.PhiriusManaUsed = self.PhiriusManaUsed + 1;		-- counts use of mana potions

						cprintf(cli.green, language[11], 		-- Using MP potion
							self.Mana, self.MaxMana, self.Mana/self.MaxMana*100,
							item.Name, item.ItemCount);
							RestWhileCheckingForWaypoint(1000)
						if( self.Fighting ) then
							yrest(1000);
						end

						self.PhiriusLastUseTime = os.time();
					end

					return true;		-- avoid invalid/use count of
				else	-- potions empty
					if( os.difftime(os.time(), self.PhiriusLastManaEmptyTime) > 16 ) then
						cprintf(cli.yellow, "No more usable MP Phirius pots\n");
						self.PhiriusLastManaEmptyTime = os.time();
						-- full inventory update if potions empty
						if( os.difftime(os.time(), self.InventoryLastUpdate) >
						  settings.profile.options.INV_UPDATE_INTERVAL ) then
							self.InventoryDoUpdate = true;
						end
					end;
				end;
			end
		end
	end

	self:updateHP()
	self:updateMP()
	self:updateCasting()

	--== Normal Potions after checking for phirius ==--
--=== Lisa to add in hot and one time heal potion usage. ===--

	-- If we need to use a heal over time potion
	if( (self.HP/self.MaxHP*100) < settings.profile.options.HP_LOW_POTION  and
		os.difftime(os.time(), self.PotionLastUseTime) > 15 )  then
		item = inventory:bestAvailableConsumable("hot");
		if( item ) then
			if self.Casting then self:waitTillCastingEnds() end -- wait if still casting minus undercut
			local checkItemName = item.Name;
			item:update();
			if( checkItemName ~= item.Name ) then
				cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
				item:update();
			else
				item:use();
				self.PotionHpUsed = self.PotionHpUsed + 1;			-- counts use of HP potions

				cprintf(cli.green, language[10], 		-- Using HP potion
				   self.HP, self.MaxHP, self.HP/self.MaxHP*100,
				   item.Name, item.ItemCount);
				   RestWhileCheckingForWaypoint(1000)
				if( self.Fighting ) then
					yrest(1000);
				end

				self.PotionLastUseTime = os.time();
			end

			return true;
		else		-- potions empty
			if( os.difftime(os.time(), self.PotionLastHpEmptyTime) > 16 ) then
				cprintf(cli.yellow, language[17], inventory.MaxSlots); 		-- No more (usable) hp potions
				self.PotionLastHpEmptyTime = os.time();
				-- full inventory update if potions empty
				if( os.difftime(os.time(), self.InventoryLastUpdate) >
				  settings.profile.options.INV_UPDATE_INTERVAL ) then
					self.InventoryDoUpdate = true;
				end
			end;
		end
	end

	-- If we need to use a mana over time potion(if we even have mana)
	if( self.MaxMana > 0 ) then
		if( (self.Mana/self.MaxMana*100) < settings.profile.options.MP_LOW_POTION  and
			os.difftime(os.time(), self.PotionLastUseTime) > 15 )  then
			item = inventory:bestAvailableConsumable("mot");
			if( item ) then
				-- yrest(settings.profile.options.SKILL_USE_PRIOR);	-- potions can be drubk before cast/skill is finished
				if self.Casting then self:waitTillCastingEnds() end -- wait if still casting minus undercut
				--local unused,unused,checkItemName = RoMScript("GetBagItemInfo(" .. item.SlotNumber .. ")");
				-- I think this check here is useless now
				local checkItemName = item.Name;
				item:update();
				if( checkItemName ~= item.Name ) then
					cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
					item:update();
				else
					item:use();
					self.PotionManaUsed = self.PotionManaUsed + 1;		-- counts use of mana potions

					cprintf(cli.green, language[11], 		-- Using MP potion
						self.Mana, self.MaxMana, self.Mana/self.MaxMana*100,
						item.Name, item.ItemCount);
						RestWhileCheckingForWaypoint(1000)
					if( self.Fighting ) then
						yrest(1000);
					end

					self.PotionLastUseTime = os.time();
				end

				return true;		-- avoid invalid/use count of
			else	-- potions empty
				if( os.difftime(os.time(), self.PotionLastManaEmptyTime) > 16 ) then
					cprintf(cli.yellow, language[16], inventory.MaxSlots); 		-- No more (usable) mana potions
					self.PotionLastManaEmptyTime = os.time();
					-- full inventory update if potions empty
					if( os.difftime(os.time(), self.InventoryLastUpdate) >
					  settings.profile.options.INV_UPDATE_INTERVAL ) then
						self.InventoryDoUpdate = true;
					end
				end;
			end;
		end
	end

	-- If we need to use a heal potion
	if( (self.HP/self.MaxHP*100) < settings.profile.options.HP_LOW_POTION  and
		os.difftime(os.time(), self.PotionLastOnceUseTime) > 60 )  then
		item = inventory:bestAvailablepotion("heal");
		if( item ) then
			if self.Casting then self:waitTillCastingEnds() end -- wait if still casting minus undercut
			local checkItemName = item.Name;
			item:update();
			if( checkItemName ~= item.Name ) then
				cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
				item:update();
			else
				item:use();
				self.PotionHpOnceUsed = self.PotionHpOnceUsed + 1;			-- counts use of HP potions

				cprintf(cli.green, language[10], 		-- Using HP potion
				   self.HP, self.MaxHP, self.HP/self.MaxHP*100,
				   item.Name, item.ItemCount);
				if( self.Fighting ) then
					yrest(1000);
				end

				self.PotionLastOnceUseTime = os.time();
			end

			return true;
		else		-- potions empty
			if( os.difftime(os.time(), self.PotionLastHpOnceEmptyTime) > 16 ) then
				--cprintf(cli.yellow, language[17], inventory.MaxSlots); 		-- No more (usable) hp potions
				self.PotionLastHpOnceEmptyTime = os.time();
				-- full inventory update if potions empty
				if( os.difftime(os.time(), self.InventoryLastUpdate) >
				  settings.profile.options.INV_UPDATE_INTERVAL ) then
					self.InventoryDoUpdate = true;
				end
			end;
		end
	end

	-- If we need to use a mana potion(if we even have mana)
	if( self.MaxMana > 0 ) then
		if( (self.Mana/self.MaxMana*100) < settings.profile.options.MP_LOW_POTION  and
			os.difftime(os.time(), self.PotionLastOnceUseTime) > 60 )  then
			item = inventory:bestAvailablepotion("mana");
			if( item ) then
				-- yrest(settings.profile.options.SKILL_USE_PRIOR);	-- potions can be drubk before cast/skill is finished
				if self.Casting then self:waitTillCastingEnds() end -- wait if still casting minus undercut
				--local unused,unused,checkItemName = RoMScript("GetBagItemInfo(" .. item.SlotNumber .. ")");
				-- I think this check here is useless now
				local checkItemName = item.Name;
				item:update();
				if( checkItemName ~= item.Name ) then
					cprintf(cli.yellow, language[18], tostring(checkItemName), tostring(item.Name));
					item:update();
				else
					item:use();
					self.PotionManaOnceUsed = self.PotionManaOnceUsed + 1;		-- counts use of mana potions

					cprintf(cli.green, language[11], 		-- Using MP potion
						self.Mana, self.MaxMana, self.Mana/self.MaxMana*100,
						item.Name, item.ItemCount);
					if( self.Fighting ) then
						yrest(1000);
					end

					self.PotionLastOnceUseTime = os.time();
				end

				return true;		-- avoid invalid/use count of
			else	-- potions empty
				if( os.difftime(os.time(), self.PotionLastManaOnceEmptyTime) > 16 ) then
					--cprintf(cli.yellow, language[16], inventory.MaxSlots); 		-- No more (usable) mana potions
					self.PotionLastManaOnceEmptyTime = os.time();
					-- full inventory update if potions empty
					if( os.difftime(os.time(), self.InventoryLastUpdate) >
					  settings.profile.options.INV_UPDATE_INTERVAL ) then
						self.InventoryDoUpdate = true;
					end
				end;
			end;
		end
	end

	return false

end

function CPlayer:fight()
	if( not self:haveTarget() ) then
		return false;
	end
	self:updateMounted()
	if self.Mounted then
		self:dismount()
	end

	if (settings.profile.options.PARTY_ICONS == true) and self.Name == getPartyLeaderName() then
		sendMacro('SetRaidTarget("target", 1);')
	end

	local target = self:getTarget();
	self.IgnoreTarget = target.Address;

	if target.MaxHP > (player.MaxHP * settings.profile.options.AUTO_ELITE_FACTOR) then
		-- check if preCodeOnElite event is used in profile
		if( type(settings.profile.events.preCodeOnElite) == "function" ) then
			releaseKeys();
			_arg1 = target
			local status,err = pcall(settings.profile.events.preCodeOnElite);
			if( status == false ) then
				local msg = sprintf(language[188], err);
				error(msg);
			end
		end
	end

	if self.Class1 == CLASS_WARDEN then -- if warden let pet start fight.
		petupdate()
		if pet.Name == GetIdName(102297) or
		pet.Name == GetIdName(102324) or
		pet.Name == GetIdName(102803)
		then
			petstartcombat()
		end
	end

	self.Fighting = true;
	cprintf(cli.green, language[22], target.Name);	-- engagin x in combat

	-- Keep tapping the attack button once every few seconds
	-- just in case the first one didn't go through
	local function timedAttack()
		self:updateTargetPtr();

		-- Prevents looting when looting is turned off
		-- (target is dead, or about to be dead)
		if self.TargetPtr == 0 then
			return
		else
			local target = CPawn.new(self.TargetPtr);
			target:updateHP()
			if (target.HP/target.MaxHP) <= 0.1 then
				return;
			end

			Attack()
		end;
	end

	-- Prep for battle, if needed.
	--self:checkSkills();

	self.FightStartTime = getGameTime();
	local move_closer_counter = 0;	-- count move closer trys
	self.Cast_to_target = 0;		-- reset counter cast at enemy target
	self.ranged_pull = false;		-- flag for timed ranged pull for melees
	local hf_start_dist = 0;		-- distance to mob where we start the fight

	-- check if timed ranged pull for melee
	self:updateBattling()
	if(settings.profile.options.COMBAT_TYPE == "melee"  and
	   settings.profile.options.COMBAT_RANGED_PULL == true and
	   self.Battling ~= true ) then
		self.ranged_pull = true;
		cprintf(cli.green, language[96]);	-- we start with ranged pulling
	end

	-- normal melee attack only if ranged pull isn't used
	if( settings.profile.options.COMBAT_TYPE == "melee" and
	    self.ranged_pull ~= true ) then
		registerTimer("timedAttack", secondsToTimer(2), timedAttack);

		-- start melee attack (even if out of range)
		timedAttack();
	end

	self.failed_casts_in_a_row = 0
	break_fight = false;	-- flag to avoid kill counts for breaked fights
	BreakFromFight = false -- For users to manually break from fight using player:breakFight()
	while( self:haveTarget() ) do
		-- If we die, break
		self:updateHP()
		self:updateAlive()
		if( self.HP < 1 or self.Alive == false ) then
			if( settings.profile.options.COMBAT_TYPE == "melee" ) then
				unregisterTimer("timedAttack");
			end
			self.Fighting = false;
			break_fight = true;
--			return;
			break;
		end;

		if BreakFromFight == true then
			break_fight = true;
			break;
		end

		local target = CPawn.new(self.TargetPtr);

		-- Long time break: Exceeded max fight time (without hurting enemy) so break fighting
		if getGameTime() - self.FightStartTime > settings.profile.options.MAX_FIGHT_TIME then
			target:updateLastHP()
			if target.LastHP == 0 or (getGameTime() - target:getLastDamage()) > settings.profile.options.MAX_FIGHT_TIME then
				printf(2 ..language[83]);			-- Taking too long to damage target
				self:addToMobIgnoreList(target.Address)
				self:clearTarget();

				self:updateBattling()
				if( self.Battling ) then
					keyboardHold( settings.hotkeys.MOVE_BACKWARD.key);
					yrest(1000);
					keyboardRelease( settings.hotkeys.MOVE_BACKWARD.key);
					self:updateXYZ();
				end

				break_fight = true;
				break;
			end
		end

		self:updateXYZ()
		target:updateXYZ()
		local dist = distance(self.X, self.Z, target.X, target.Z);
		if( hf_start_dist == 0 ) then		-- remember distance we start the fight
			hf_start_dist = dist;
		end

		-- check if pulling phase should be finished
		if( self.ranged_pull == true ) then
			if( dist <= settings.options.MELEE_DISTANCE ) then
				cprintf(cli.green, language[97]); -- Ranged pulling finished, mob in melee distance
				self.ranged_pull = false;
			elseif( os.difftime(os.time(), self.aggro_start_time) > 3 and
			  self.aggro_start_time ~= 0 ) then
				cprintf(cli.green, language[98]); -- Ranged pulling after 3 sec wait finished
				self.ranged_pull = false;
			elseif( dist >=  hf_start_dist-45 and	-- mob not really coming closer
			  os.difftime(os.time(), self.aggro_start_time) > 1  and
			  self.aggro_start_time ~= 0 ) then
				cprintf(cli.green, language[99]); -- Ranged pulling finished. Mob not really moving
				self.ranged_pull = false;
			end;

			if self.ranged_pull == false and settings.profile.options.COMBAT_TYPE == "melee" then
				registerTimer("timedAttack", secondsToTimer(2), timedAttack);
				timedAttack();
			end
		end

		-- We're a bit TOO close...
		self:updateCasting()
		if( dist < 5.0 and not self.Casting ) then
			printf(language[24]);
			keyboardHold( settings.hotkeys.MOVE_BACKWARD.key);
			yrest(200);
			keyboardRelease( settings.hotkeys.MOVE_BACKWARD.key);
			self:updateXYZ();
			dist = distance(self.X, self.Z, target.X, target.Z);
		end

		-- Move closer to the target if needed
		local suggestedRange = settings.options.MELEE_DISTANCE;
		if( suggestedRange == nil ) then suggestedRange = 45; end;
		if( settings.profile.options.COMBAT_TYPE == "ranged" or
		  self.ranged_pull == true ) then
			if( settings.profile.options.COMBAT_DISTANCE ~= nil ) then
				suggestedRange = settings.profile.options.COMBAT_DISTANCE;
			else
				suggestedRange = 155;
			end
		end

		-- check if aggro before attacking
		target:updateLastHP()
		target:updateTargetPtr()
		self:updateBattling()
		if( self.Battling == true  and				-- we have aggro
		    target.LastHP == 0 and		-- we haven't started attacking it yet
		    target.TargetPtr ~= self.Address and
			not target:targetIsFriend()) then	-- but not from that mob
				target:updateName()
				local enemy = player:findEnemy(true,nil,evalTargetDefault) -- find enemy
				if enemy and enemy.Address ~= self.TargetPtr then -- It's not the one we targeting
					cprintf(cli.green, language[36], target.Name); -- Aggro during first strike
					self:clearTarget();
					break_fight = true;
					break;
				end
		end;

		if( dist > suggestedRange and not self.Casting ) then

			-- count move closer and break if to much
			move_closer_counter = move_closer_counter + 1;		-- count our move tries
			if( move_closer_counter > 3  and
			  (settings.profile.options.COMBAT_TYPE == "ranged" or
			  self.ranged_pull == true) ) then
				cprintf(cli.green, language[84]);	-- To much tries to come closer
				self:clearTarget();
				break_fight = true;
				break;
			end

			printf(language[25], suggestedRange, dist);
			-- move into distance
			local success, reason;

			if( settings.profile.options.COMBAT_TYPE == "ranged" or
			  self.ranged_pull == true ) then		-- melees with timed ranged pull
				if dist > suggestedRange then -- move closer
					success, reason = self:moveTo(target, true, true, suggestedRange);
				end
			else 	-- normal melee
				success, reason = self:moveTo(target, true, false, 50);
				-- Start melee attacking
				if( settings.profile.options.COMBAT_TYPE == "melee" ) then
					timedAttack();
				end
			end

			-- Lost target during moveTo
			if not self:haveTarget() then
				cprintf(cli.green, language[19]);	-- Target lost
				self:clearTarget();
				break_fight = true;
				break;
			end

			if( not success ) then
				self:unstick();
			end

		elseif suggestedRange >= dist and (settings.profile.options.COMBAT_STOP_DISTANCE == nil or dist <= settings.profile.options.COMBAT_STOP_DISTANCE) then
			keyboardRelease( settings.hotkeys.MOVE_FORWARD.key);
		end

		faceTarget()

		if self:checkPotions() or self:checkSkills()  then
			-- If we used a potion or a skill, reset our last dist improvement
			-- to prevent unsticking
			self.LastDistImprove = os.time();
		elseif self.Cast_to_target == 0 then
			self.ranged_pull = false
		end

		if break_fight then -- if triggered in checkskills
			cprintf(cli.green, language[19]);	-- Target lost
			break
		end
		yrest(100);
	end

	self:resetSkills();
	self.Cast_to_target = 0;	-- reset cast to target counter

	if( settings.profile.options.COMBAT_TYPE == "melee" ) then
		unregisterTimer("timedAttack");
	end

	target:updateAlive()
	if( not break_fight) and not target:isAlive() then
		-- count kills per target name
		local target_Name = target.Name;
		if(target_Name == nil) then  target_Name = "<UNKNOWN>"; end;
		if(self.mobs[target_Name] == nil) then  self.mobs[target_Name] = 0; end;
		self.mobs[target_Name] = self.mobs[target_Name] + 1;

		self.Fights = self.Fights + 1;		-- count our fights

		cprintf(cli.green, language[27], 	-- Fight finished. Target dead/lost
		  self.mobs[target_Name],
		  target_Name,
		  self.Fights,
		  os.difftime(os.time(),
		  self.BotStartTime_nr)/60);
	else
		cprintf(cli.green, language[177]); 	-- Fight aborted
	end

	-- If still casting clicktocast skill and mobs still in range then keep casting
	self:updateCasting()
	while player.Casting and self.LastSkill.ClickToCast and
		(self.LastSkill.Type == STYPE_DAMAGE or	self.LastSkill.Type == STYPE_DOT ) and
		mobsInRangeOfLastClickToCast() do
			yrest(100)
			self:updateCasting()
	end

	self:updateCasting()
	if self.Casting and self.LastSkill.ClickToCast then
		keyboardPress(settings.hotkeys.MOVE_BACKWARD.key)
	end


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


	-- give client a little time to update battle flag (to come out of combat),
	-- if we loot even at combat we don't need the time
	--[[if( settings.profile.options.LOOT == true  and
		settings.profile.options.LOOT_IN_COMBAT ~= true ) then
--		yrest(800);
		inventory:updateSlotsByTime(800);

	end;]]
	-- Monster is dead (0 HP) but still targeted.
	-- Loot and clear target.
	if( not break_fight ) then
		self:loot();
	end

	keyboardRelease( settings.hotkeys.MOVE_FORWARD.key);

	-- Loot any other dead monsters nearby
	self:updateBattling()
	if not self.Battling or not self:findEnemy(true,nil,evalTargetDefault) then
		self:lootAll()
	end

	if( self.TargetPtr ~= 0 ) then
		self:clearTarget();
	end

	self.Fighting = false;
	self.LastSkill = {}

	yrest(200);
end

function CPlayer:breakFight()
	BreakFromFight = true
end

function CPlayer:loot()

	if( settings.profile.options.LOOT ~= true and settings.profile.options.LOOT_SIGILS ~= true) then
		if( settings.profile.options.DEBUG_LOOT) then
			cprintf(cli.yellow, "[DEBUG] don't loot reason: settings.profile.options.LOOT ~= true and settings.profile.options.LOOT_SIGILS ~= true\n");
		end;
		return
	end

	if settings.profile.options.LOOT == true then repeat -- 'repeat' block to 'break' from 'if' statement
		self:updateTargetPtr()
		if( self.TargetPtr == 0 ) then
			if( settings.profile.options.DEBUG_LOOT) then
				cprintf(cli.yellow, "[DEBUG] don't loot reason: self.TargetPtr == 0\n");
			end;
			break
		end

		-- aggro and not loot in combat
		self:updateBattling()
		if( self.Battling  and
			settings.profile.options.LOOT_IN_COMBAT ~= true ) and
			self:findEnemy(true, nil, evalTargetDefault) then
			self:clearTarget()
			cprintf(cli.green, language[178]); 	-- Loot skiped because of aggro
			return
		end

		local target = CPawn.new(self.TargetPtr);

		if not target:exists() then
			if( settings.profile.options.DEBUG_LOOT) then
				cprintf(cli.yellow, "[DEBUG] don't loot reason: target == nil or target.Address == 0\n");
			end;
			break;
		end

		self:updateXYZ()
		target:updateXYZ()
		local dist = distance(self.X, self.Z, target.X, target.Z);
		local hf_x = self.X
		local hf_z = self.Z;
		local lootdist = 100;

		-- If already in looting range then stop
		if dist < 20 then
			keyboardRelease( settings.hotkeys.MOVE_FORWARD.key);
		end

		-- Set to combat distance; update later if loot distance is set
		if( settings.profile.options.COMBAT_TYPE == "ranged" ) then
			lootdist = settings.profile.options.COMBAT_DISTANCE;
		end

		if( settings.profile.options.LOOT_DISTANCE ) then
			lootdist = settings.profile.options.LOOT_DISTANCE;
		end

		if( dist > lootdist ) then 	-- only loot when close by
			cprintf(cli.green, language[32]);	-- Target too far away; not looting.
			break
		end


		local function looten()
			-- "attack" is also the hotkey to loot, strangely.
			--[[local hf_attack_key;
			if( settings.profile.hotkeys.MACRO ) then
				hf_attack_key = "MACRO";
				cprintf(cli.green, language[31],
				   hf_attack_key , dist);	-- looting target.
				RoMScript("UseSkill(1,1);");
			else
				hf_attack_key = getKeyName(settings.profile.hotkeys.ATTACK.key);
				cprintf(cli.green, language[31],
				   hf_attack_key , dist);	-- looting target.
				keyboardPress(settings.profile.hotkeys.ATTACK.key);
			end]]
			local jumped = false
			if settings.profile.options.LOOT_JUMPING then
				faceTarget()
				yrest(100)
				if dist < 50 then
					if dist >17 then
						keyboardHold(settings.hotkeys.MOVE_FORWARD.key) yrest(100)
					end
					keyboardPress(settings.hotkeys.JUMP.key) yrest(100)
					jumped = true
				end
			end
			Attack()
			keyboardRelease( settings.hotkeys.MOVE_FORWARD.key);
			yrest(200)
			self:updateSpeed()
			local maxWaitTime = dist*1000/self.Speed -- more accurate calculation of how long it takes to walk there
			local startWait = getTime()

			-- Move to loot
			if dist >= 50 then
				target:updateLootable()
				self:updateStance()
				while target.Lootable == true and self.Stance == 0 and deltaTime(getTime(), startWait) < maxWaitTime do
					self:updateActualSpeed()
					if self.ActualSpeed == 0 then Attack() yrest(100) end
					if settings.profile.options.LOOT_JUMPING and not jumped and maxWaitTime - deltaTime(getTime(),startWait) < 1000 then
						keyboardPress(settings.hotkeys.JUMP.key)
						jumped = true
					end
					yrest(100)
					target:updateLootable()
					self:updateStance()
				end
			end

			-- Wait LOOT_TIME
			while target.Lootable == true and self.Stance == 0 and deltaTime(getTime(), startWait) < maxWaitTime + settings.profile.options.LOOT_TIME do
				yrest(100)
				target:updateLootable()
				self:updateStance()
			end

			-- Wait for character to finish standing
			local starttime = os.clock()
			self:updateStance()
			while self.Stance ~= 0 and 2 > (os.clock() - starttime) do
				yrest(50)
				self:updateStance()
			end
		end

		target:updateLootable();
		local lootStart = os.clock()
		while target:exists() and (not target.Lootable) and os.clock() - lootStart < .2 do
			yrest(20)
			target:updateLootable()
		end

		if target.Lootable then
			looten();
			yrest(200) -- Wait a bit so it doesn't loot twice.
		else
			if( settings.profile.options.DEBUG_LOOT) then
				cprintf(cli.yellow, "[DEBUG] don't loot reason: target not lootable.\n");
			end;
		end;
		-- check for loot problems to give a noob mesassage
		self:updateTargetPtr()
		target = CPawn.new(self.TargetPtr)
		if target:exists() then
			target:updateLootable()
			if target.Lootable == true then	-- death mob disapeared?
				cprintf(cli.green, language[100]); -- We didn't move to the loot!?

				-- second loot try?
				if( type(settings.profile.options.LOOT_AGAIN) == "number" and settings.profile.options.LOOT_AGAIN > 0 ) then
					yrest(settings.profile.options.LOOT_AGAIN);
					looten();	-- try it again
				end
				-- Add to ignore list
				lootIgnoreListPos = lootIgnoreListPos + 1
				if lootIgnoreListPos > settings.profile.options.LOOT_IGNORE_LIST_SIZE then lootIgnoreListPos = 1 end
				lootIgnoreList[lootIgnoreListPos] = target.Address
			end;
		end

		-- rnd pause from 2-6 sec after loot to look more human
		if( settings.profile.options.LOOT_PAUSE_AFTER > 0 ) then
			self:restrnd( settings.profile.options.LOOT_PAUSE_AFTER,2,6);
		end;

		-- Close the booty bag.
		RoMCode("BootyFrame:Hide()");
	until true end -- 'end' ends the 'if' statement

	local function sigilNameMatch(_name)
		if settings.profile.options.SIGILS_IGNORE_LIST == nil then
			return true -- collect all sigils
		end

		for sigil in string.gmatch(";"..settings.profile.options.SIGILS_IGNORE_LIST,"[,;]([^,;]*)") do
			if string.match(sigil,"^'.*'$") then sigil = string.match(sigil,"^'(.*)'$") end
			if sigil == _name then
				return false -- ignore the sigil
			end
		end

		return true -- not in ignore list. Don't ignore.
	end

	local function getNearestSigil()
		local nearestSigil = nil;
		local obj = nil;
		local objectList = CObjectList();
		objectList:update();
		self:updateXYZ()

		for i = 0,objectList:size() do
			obj = objectList:getObject(i);

			if( obj ~= nil ) and ( obj.Type == PT_SIGIL ) and sigilNameMatch(obj.Name) then

				local dist = distance(self.X, self.Z, obj.X, obj.Z);

				if( nearestSigil == nil and dist < settings.profile.options.LOOT_DISTANCE ) then
					nearestSigil = obj;
				else

					if( dist < settings.profile.options.LOOT_DISTANCE and
						dist < distance(self.X, self.Z, nearestSigil.X, nearestSigil.Z) ) then
						-- New nearest sigil found
						nearestSigil = obj;
					end
				end
			end
		end

		return nearestSigil;
	end

	if settings.profile.options.LOOT_SIGILS == true or (settings.profile.options.LOOT == true and settings.profile.options.LOOT_SIGILS == nil) then
		-- Pick up all nearby sigils
		self:clearTarget();
		local sigil = getNearestSigil();
		if( sigil ) then
			self:updateXYZ()
			local dist = distance(self.X, self.Z, self.Y, sigil.X, sigil.Z, sigil.Y);
			local angle = math.atan2(sigil.Z - self.Z, sigil.X - self.X);
			local yangle = math.atan2(sigil.Y - self.Y, ((sigil.X - self.X)^2 + (sigil.Z - self.Z)^2)^.5 );
			local nY = self.Y + math.sin(yangle) * (dist + 15);
			local hypotenuse = (1 - math.sin(yangle)^2)^.5
			local nX = self.X + math.cos(angle) * (dist + 15) * hypotenuse;
			local nZ = self.Z + math.sin(angle) * (dist + 15) * hypotenuse;
			printf("Picking up sigil \"%s\"\n",sigil.Name)

			self:moveTo( CWaypoint(nX, nZ, nY), true );
			yrest(500);
			sigil = getNearestSigil();
		end
	end
end

function evalTargetLootable(address, target)

	if not target or not target.HP then
		target = CPawn.new(address)
	end
	-- Check if still valid target
	if not target:exists() then
		return false
	end

	-- Check if lootable
	target:updateLootable()
	if not ( target.Lootable ) then
		return false;
	end

	-- Check if in lootIgnoreList
	for __, addr in pairs(lootIgnoreList) do
		if target.Address == addr then
			return false
		end
	end

	-- Check height difference
	target:updateXYZ()
	if( math.abs(target.Y - player.Y) > 45 ) then
		return false;
	end

	-- check distance to target
	local dist = distance(player.X, player.Z, player.Y, target.X, target.Z, target.Y);
	local lootdist = 100;

	-- Set to combat distance; update later if loot distance is set
	if( settings.profile.options.COMBAT_TYPE == "ranged" ) then
		lootdist = settings.profile.options.COMBAT_DISTANCE;
	end

	if( settings.profile.options.LOOT_DISTANCE ) then
		lootdist = settings.profile.options.LOOT_DISTANCE;
	end

	if( dist > lootdist ) then 	-- only loot when close by
		return false
	end

	-- check target distance to path against MAX_TARGET_DIST
	local wpl; -- this is the waypoint list we're using
	local V; -- this is the point we will use for distance checking

	if( player.Returning ) then
		wpl = __RPL;
	else
		wpl = __WPL;
	end

	if (__WPL:getMode() == "waypoints") and #__WPL.Waypoints > 0 then
		local pA = wpl.Waypoints[wpl.LastWaypoint]
		local pB = wpl.Waypoints[wpl.CurrentWaypoint]

		V = getNearestSegmentPoint(player.X, player.Z, pA.X, pA.Z, pB.X, pB.Z);
	else
		V = CWaypoint(player.X, player.Z); -- Distance check from player in wander mode
	end

	-- use a bounding box first to avoid sqrt when not needed (sqrt is expensive)
	if( target.X > (V.X - lootdist) and
		target.X < (V.X + lootdist) and
		target.Z > (V.Z - lootdist) and
		target.Z < (V.Z + lootdist) ) then

		if( distance(V.X, V.Z, target.X, target.Z) > lootdist ) then
			if( settings.profile.options.DEBUG_LOOT) then
				cprintf(cli.yellow, "unlooted monster dist > lootdist")
			end
			return false;			-- he is not a valid target
		end;
	else
		-- must be too far away
		if( settings.profile.options.DEBUG_LOOT) then
			cprintf(cli.yellow, "unlooted monster dist > lootdist")
		end
		return false;
	end

	return true
end

function CPlayer:lootAll()
	if( settings.profile.options.LOOT ~= true ) then
		if( settings.profile.options.DEBUG_LOOT) then
			cprintf(cli.yellow, "[DEBUG] don't loot all reason: settings.profile.options.LOOT ~= true\n");
		end;
		return
	end

	if( settings.profile.options.LOOT_ALL ~= true ) then
		if( settings.profile.options.DEBUG_LOOT) then
			cprintf(cli.yellow, "[DEBUG] don't loot all reason: settings.profile.options.LOOT_ALL ~= true\n");
		end;
		return
	end

	-- Warn user if they still have 'lootbodies()' userfunction installed.
	if type(lootBodies) == "function" then
		cprintf(cli.yellow,"The userfunction 'lootBodies()' is obsolete and might interfere with the bots 'lootAll()' function. Please delete the 'addon_lootbodies.lua' file from the 'userfunctions' folder.\n")
	end


	while true do
		-- Check if inventory is full. We don't loot if inventory is full.
		if inventory:itemTotalCount(0) == 0 then
			if( settings.profile.options.DEBUG_LOOT) then
				cprintf(cli.yellow, "[DEBUG] don't loot all reason: inventory is full\n");
			end;
			return
		end

		self:updateBattling()
		if( self.Battling  and
			self:findEnemy(true,nil,evalTargetDefault)) then
			break
		end

		local Lootable = self:findNearestNameOrId("", nil, evalTargetLootable)

		if Lootable == nil then
			break
		else
			Lootable = CPawn(Lootable.Address)
		end

		self:target(Lootable)
		self:updateTargetPtr()
		if self.TargetPtr ~= 0 then -- Target's still there.
			self:loot()
			if self:findEnemy(true, nil, evalTargetDefault) then
				-- not looting because of aggro
				return
			end
			yrest(50)
			Lootable:updateLootable();
			if Lootable.Lootable == true then
				-- Failed to loot. Add to ignore list
				lootIgnoreListPos = lootIgnoreListPos + 1
				if lootIgnoreListPos > settings.profile.options.LOOT_IGNORE_LIST_SIZE then lootIgnoreListPos = 1 end
				lootIgnoreList[lootIgnoreListPos] = Lootable.Address
			end
		end
	end
end

-- Basic target evaluation.
-- Returns true if a valid target, else false.
function evalTargetDefault(address, target)
	if not target then
		target = CPawn.new(address);
	end

	--== Helper Functions ==--
	--------------------------

	local function debug_target(_place)
		if settings.profile.options.DEBUG_TARGET and
		   player.TargetPtr ~= player.LastTargetPtr then
			cprintf(cli.yellow, "[DEBUG] "..(target.Address or 0).." ".._place.."\n");
			player.LastTargetPtr = player.TargetPtr;		-- remember target address to avoid msg spam
		end
	end

	local function printNotTargetReason(_reason)
		if( player.TargetPtr ~= player.LastTargetPtr ) then
			cprintf(cli.yellow, "%s\n", _reason);
			player.LastTargetPtr = player.TargetPtr;		-- remember target address to avoid msg spam
		end
	end

	--== First do checks that target is valid and alive ==--
	--------------------------------------------------------


	-- Check if still valid target
	if not target:exists() then
		debug_target("target is no longer valid")
		return false
	end

	-- Can't have self as target
	if( address == player.Address ) then
		debug_target("Can't have self as target")
		return false;
	end

	-- Not attackable
	target:updateAttackable()
	if( not target.Attackable ) then
		debug_target("target is not attackable")
		return false;
	end

	-- Dead
	target:updateHP()
	if( target.HP < 1 ) then
		debug_target("target HP is less than 1")
		return false;
	end

	-- Also dead (and has loot)
	target:updateLootable()
	if( target.Lootable ) then
		debug_target("target is lootable therefore dead")
		return false;
	end

	target:updateAlive()
	if( not target.Alive ) then
		debug_target("target is not Alive")
		return false;
	end

	--== Check aggro ==--
	---------------------

	target:updateTargetPtr()
	target:updateType()
	player:updateBattling()
	if player.Battling then -- Battling flag is on
		if target.TargetPtr == player.Address or -- We are being targeted
		  target:targetIsFriend(true) then -- Or friend is being targeted
			if target.Type ~= PT_PLAYER or settings.profile.options.PVP ~= false then --  Check PVP
				return true
			end
		end
	end

	--== Non aggro checks ==--
	--------------------------

	-- don't target NPCs
	if( target.Type == PT_NPC ) then      -- NPCs are type == 4
		debug_target("thats a NPC, he should be friendly and not attackable")
		return false;         -- he is not a valid target
	end;

	-- Check height difference
	target:updateXYZ()
	player:updateXYZ()
	if( math.abs(target.Y - player.Y) > 45 ) then
		debug_target("target height difference is too great")
		return false;
	end

	-- check level of target against our leveldif settings
	target:updateLevel()
	if( ( target.Level - player.Level ) > tonumber(settings.profile.options.TARGET_LEVELDIF_ABOVE)  or
	( player.Level - target.Level ) > tonumber(settings.profile.options.TARGET_LEVELDIF_BELOW)  ) then
		debug_target("target lvl above/below profile settings without battling")
		return false;			-- he is not a valid target
	end;

	-- check if on the ignore list
	if target:isOnMobIgnoreList() then
		target:updateName()
		cprintf(cli.green, language[87], target.Name);
		debug_target("ignore target (e.g. after doing no damage")
		return false
	end

	-- check distance to target against MAX_TARGET_DIST
	if( distance(player.X, player.Z, target.X, target.Z) > settings.profile.options.MAX_TARGET_DIST ) then
		debug_target("target dist > MAX_TARGET_DIST to player")
		return false;			-- he is not a valid target
	end;

	-- check if in assigned kill zone
	if (not player.Returning) and #__WPL.KillZone > 0 and not PointInPoly(__WPL.KillZone, target.X, target.Z) then
		debug_target("target outside KillZone")
		return false;			-- he is not a valid target
	end

	-- check if in one of the exclude zones
	if (not player.Returning) and next(__WPL.ExcludeZones) then
		for zonename,zone in pairs(__WPL.ExcludeZones) do
			if PointInPoly(zone, target.X, target.Z) then
				debug_target("target inside an exclude zone")
				return false;			-- he is not a valid target
			end
		end
	end

	-- check target distance to path against MAX_TARGET_DIST
	local wpl; -- this is the waypoint list we're using
	local V; -- this is the point we will use for distance checking

	if( player.Returning ) then
		wpl = __RPL;
	else
		wpl = __WPL;
	end

	if (__WPL:getMode() == "waypoints") and #__WPL.Waypoints > 0 then
		local pA = wpl.Waypoints[wpl.LastWaypoint]
		local pB = wpl.Waypoints[wpl.CurrentWaypoint]

		V = getNearestSegmentPoint(player.X, player.Z, pA.X, pA.Z, pB.X, pB.Z);
	else
		V = CWaypoint(player.X, player.Z); -- Distance check from player in wander mode
	end

	-- use a bounding box first to avoid sqrt when not needed (sqrt is expensive)
	if( target.X > (V.X - settings.profile.options.MAX_TARGET_DIST) and
		target.X < (V.X + settings.profile.options.MAX_TARGET_DIST) and
		target.Z > (V.Z - settings.profile.options.MAX_TARGET_DIST) and
		target.Z < (V.Z + settings.profile.options.MAX_TARGET_DIST) ) then

		if( distance(V.X, V.Z, target.X, target.Z) > settings.profile.options.MAX_TARGET_DIST ) then
			debug_target("target dist > MAX_TARGET_DIST to waypoint")
			return false;			-- he is not a valid target
		end;
	else
		-- must be too far away
		debug_target("target dist > MAX_TARGET_DIST to waypoint")
		return false;
	end


	-- PK protect
	if settings.profile.options.PVP == false then
		if( target.Type == PT_PLAYER ) then
			debug_target("target is a player. PVP is off.")
			return false;
		end
	elseif settings.profile.options.PVP ~= true then
		if( target.Type == PT_PLAYER ) then      -- Player are type == 1
			debug_target("PK player, but not fighting us")
			return false;         -- he is not a valid target
		end;
	end

	-- Ignore pets
	target:updateIsPet()
	if target.IsPet then
		debug_target("target is a pet")
		return false
	end

	-- Friends aren't enemies
	if( target:isFriend() ) then
		debug_target("target is a friend")
		return false;		-- he is not a valid target
	end;

	-- Mob limitations defined?
	if( #settings.profile.mobs > 0 ) then
		if( player:isInMobs(target) == false ) then
			debug_target("mob limitation is set, mob is not a valid target")
			return false;		-- he is not a valid target
		end
	end;

	-- target is to strong for us
	if (settings.profile.options.PARTY_INSTANCE ~= true ) then
		if( target.MaxHP > player.MaxHP * settings.profile.options.AUTO_ELITE_FACTOR ) then
--				debug_target("target is to strong. More HP then self.MaxHP * settings.profile.options.AUTO_ELITE_FACTOR")
			printNotTargetReason("Target is to strong. More HP then player.MaxHP * settings.profile.options.AUTO_ELITE_FACTOR")
			return false;		-- he is not a valid target
		end;
	end

	if( settings.profile.options.ANTI_KS ) then
		target:updateTargetPtr()
		if target.TargetPtr ~= player.Address and not target:targetIsFriend() then
			-- If the target's TargetPtr is 0,
			-- that doesn't necessarily mean they don't
			-- have a target (game bug, not a bug in the bot)
			if( target.TargetPtr == 0 ) then
				if( target.HP < target.MaxHP ) then
					debug_target("anti kill steal: target not fighting us: unknown target")
					return false;
				end
			else
				local targettarget = CPawn.new(target.TargetPtr)
				targettarget:updateType()
				if targettarget.Type == PT_PLAYER then
					-- They definitely target another player.
					-- If it is a friend, we can help.
					-- Otherwise, leave it alone.
					debug_target("anti kill steal: target not fighting us: target isn't targeting a friend")
					return false;
				end
			end
		end
	end

	return true;
end

function CPlayer:moveTo(waypoint, ignoreCycleTargets, dontStopAtEnd, range)
	if settings.profile.options.PARTYLEADER_WAIT and GetPartyMemberName(1) then
		if not checkparty(150) then
			releaseKeys()
			repeat yrest(500) self:updateBattling() until checkparty(150) or self.Battling
		end
	end
	local function passed_point(lastpos, point)
		point.X = tonumber(point.X)
		point.Z = tonumber(point.Z)

		local posbuffer = 5

		local passed = true
		if lastpos.X < point.X and self.X < point.X - posbuffer then
			return false
		end
		if lastpos.X > point.X and self.X > point.X + posbuffer then
			return false
		end
		if lastpos.Z < point.Z and self.Z < point.Z - posbuffer then
			return false
		end
		if lastpos.Z > point.Z and self.Z > point.Z + posbuffer then
			return false
		end

		return true
	end

	self:updateXYZ();

	local angle = math.atan2(waypoint.Z - self.Z, waypoint.X - self.X);
	local yangle = 0
	if waypoint.Y ~= nil then
		yangle = math.atan2(waypoint.Y - self.Y, ((waypoint.X - self.X)^2 + (waypoint.Z - self.Z)^2)^.5 );
	end
	self:updateDirection()
	local angleDif = angleDifference(angle, self.Direction);
	local startTime = os.time();
	ignoreTargets = ignoreTargets or false;

	if( ignoreCycleTargets == nil ) then
		ignoreCycleTargets = false;
	end;

	if( waypoint.Type == WPT_TRAVEL or waypoint.Type == WPT_RUN ) then
		if( settings.profile.options.DEBUG_TARGET ) then
			cprintf(cli.yellow, "[DEBUG] waypoint type RUN or TRAVEL. We don't target mobs.\n");
		end
		ignoreCycleTargets = true;	-- don't target mobs
	end;

	-- Make sure we don't have a garbage (dead) target
	self:updateTargetPtr()
	if( self.TargetPtr ~= 0 ) then
		local target = CPawn.new(self.TargetPtr)
		if target:exists() then -- Target exists
			target:updateHP()
			if( target.HP <= 1 ) then
				self:clearTarget();
			end
		end
	end

	-- no active turning and moving if wander and radius = 0
	-- self direction has values from 0 til Pi and -Pi til 0
	-- angle has values from 0 til 2*Pi
	if(__WPL:getMode()   == "wander"  and
	   __WPL:getRadius() == 0     )   then
	   	self:restrnd(100, 1, 4);	-- wait 3 sec

		self:updateDirection()
		angle = self.Direction

		-- we will not move back to WP if wander and radius = 0
		-- so one can move the character manual and use the bot only as fight support
		-- there we set the WP to the actual player position
		self:updateXYZ()
		waypoint.Z = self.Z;
		waypoint.X = self.X;

	end;

	-- QUICK_TURN only
	if( settings.profile.options.QUICK_TURN == true ) then
		self:faceDirection(angle, yangle);
		camera:setRotation(angle);
		angleDif = angleDifference(angle, self.Direction);
	else
		self:faceDirection(self.Direction, yangle); -- change only 'Y' angle with 'faceDirection'.
	end

	-- If more than X degrees off, correct before moving.
	local rotateStartTime = os.time();
	local turningDir = -1; -- 0 = left, 1 = right
	while( angleDif > math.rad(65) ) do
		self:updateHP()
		self:updateAlive()
		if( self.HP < 1 or self.Alive == false ) then
			return false, WF_NONE;
		end;

		if( os.difftime(os.time(), rotateStartTime) > 3.0 ) then
			-- Sometimes both left and right rotate get stuck down.
			-- Press them both to make sure they are fully released.
			keyboardPress(settings.hotkeys.ROTATE_LEFT.key);
			keyboardPress(settings.hotkeys.ROTATE_RIGHT.key);

			-- we seem to have been distracted, take a step back.
			keyboardPress(settings.hotkeys.MOVE_BACKWARD.key);
			rotateStartTime = os.time();
		end

		if( angleDifference(angle, self.Direction + 0.01) < angleDif ) then
			-- rotate left
			keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
			keyboardHold( settings.hotkeys.ROTATE_LEFT.key );

			--self:faceDirection( angle );
		else
			-- rotate right
			keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
			keyboardHold( settings.hotkeys.ROTATE_RIGHT.key );

			--self:faceDirection( angle );
		end

		yrest(50);
		self:updateDirection();
		angleDif = angleDifference(angle, self.Direction);
	end

	keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
	keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );

	-- look for a target before start movig
	self:updateBattling()
	if((not self.Fighting) and (not ignoreCycleTargets)) then
		if self:target(self:findEnemy(false, nil, evalTargetDefault, self.IgnoreTarget)) then	-- find a new target
			cprintf(cli.turquoise, language[86]);	-- stopping waypoint::target acquired before moving
			success = false;
			failreason = WF_TARGET;
			return success, failreason;
		end;
	end;

	-- Direction ok, start moving forward
	local success, failreason = true, WF_NONE;
	local dist = distance(self.X, self.Z, self.Y, waypoint.X, waypoint.Z, waypoint.Y);
	local lastDist = dist;
	local lastpos = {X=self.X, Z=self.Z, Y=self.Y}
	self.LastDistImprove = os.time();	-- global, because we reset it whil skill use

	local turning = false

	local loopstart
	local loopduration = 100 -- The duration we want the loop to take
	local successdist = 10
	repeat
		player:checkAddress()
		loopstart = os.clock()

		dist = distance(self.X, self.Z, self.Y, waypoint.X, waypoint.Z, waypoint.Y);
		angle = math.atan2(waypoint.Z - self.Z, waypoint.X - self.X);

		self:updateHP()
		self:updateAlive()
		if( self.HP < 1 or self.Alive == false ) then
			return false, WF_NONE;
		end;

		-- stop moving if aggro, bot will stand and wait until to get the target from the client
	 	-- only if not in the fight stuff coding (means self.Fighting == false )
		self:updateBattling()
	 	if( self.Battling and 				-- we have aggro
	 	    self.Fighting == false  and		-- we are not coming from the fight routines (bec. as melee we should move in fight)
			waypoint.Type ~= WPT_TRAVEL ) then	-- only stop if not waypoint type TRAVEL
			if self:target(self:findEnemy(true, nil, evalTargetDefault)) then
				keyboardRelease( settings.hotkeys.MOVE_FORWARD.key );
				keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
				keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
				success = false;
				failreason = WF_COMBAT;
				break;
			end
		end;

		-- look for a new target while moving
		if((not ignoreCycleTargets) and (not self.Fighting) and (not turning)) then
			if self:target(self:findEnemy(false, nil, evalTargetDefault, self.IgnoreTarget)) then
				cprintf(cli.turquoise, language[28]);	-- stopping waypoint::target acquired
				success = false;
				failreason = WF_TARGET;
				break;
			end;
		end

		-- We're still making progress
		if( dist < lastDist ) then
			self.LastDistImprove = os.time();
			lastDist = dist;
			lastpos = {X=self.X, Z=self.Z, Y=self.Y}
		elseif(  dist > lastDist + 40 ) then
			-- Check if pulled back before last waypoint
			local lastwp = __WPL:getNextWaypoint(-1)
			if (lastwp.X ~= 0 or lastwp.Z ~= 0 or lastwp.Y ~= nil) and distance(player, lastpos) > distance(lastwp, lastpos) then
				print("Was pulled back. Reseting waypoint.")
				success = false
				failreason = WF_PULLBACK
			else
				-- Make sure we didn't pass it up
				printf(language[29]);
				success = false;
				failreason = WF_DIST;
			end
			break;
		end;

		if( os.difftime(os.time(), self.LastDistImprove) > 3 ) then
			-- We haven't improved for 3 seconds, assume stuck
			success = false;
			failreason = WF_STUCK;
			break;
		end

		-- while moving without target: check potions / friendly skills
		self:updateMounted()
		if not self.Mounted and ( self:checkPotions() or self:checkSkills(ONLY_FRIENDLY) ) then	-- only cast friendly spells to ourselfe
			-- If we used a potion or a skill, reset our last dist improvement
			-- to prevent unsticking
			self.LastDistImprove = os.time();

			-- Wait for casting to finish if still casting last skill
			self:updateCasting()
			while self.Casting do
				yrest(50)
				self:updateCasting()
			end

		end

		-- Check if within range if range specified
		if range and range > dist then
			-- within range
			break
		end

		-- Check if past waypoint
		if passed_point(lastpos, waypoint) then
		   -- waypoint reached
		   break
		end

		-- Check if close to waypoint.
		if dist < successdist then
			break
		end

		if waypoint.Y ~= nil then
			yangle = math.atan2(waypoint.Y - self.Y, ((waypoint.X - self.X)^2 + (waypoint.Z - self.Z)^2)^.5 );
		end
		self:updateDirection()
		angleDif = angleDifference(angle, self.Direction);

		yrest(1) -- This is to fix a bug that causes the bot to continue moving forward after it has been paused.

		-- Continue to make sure we're facing the right direction
		if( settings.profile.options.QUICK_TURN and angleDif > math.rad(1) ) then
			self:faceDirection(angle, yangle);
			camera:setRotation(angle);
			angleDif = angleDifference(angle, self.Direction);
			keyboardHold( settings.hotkeys.MOVE_FORWARD.key );
		else
			self:faceDirection(self.Direction, yangle); -- change only 'Y' angle with 'faceDirection'.

			if( angleDif > math.rad(15) ) then
				--keyboardRelease( settings.hotkeys.MOVE_FORWARD.key );
				--keyboardRelease( settings.hotkeys.MOVE_BACKWARD.key );

				if( angleDifference(angle, self.Direction + 0.01) < angleDif ) then
						keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
						keyboardHold( settings.hotkeys.ROTATE_LEFT.key );
						yrest(100);
				else
						keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
						keyboardHold( settings.hotkeys.ROTATE_RIGHT.key );
						yrest(100);
				end
				turning = true
			elseif( angleDif > math.rad(1) ) then
				if( settings.profile.options.QUICK_TURN ) then
					camera:setRotation(angle);
				end

				self:faceDirection(angle, yangle);
				keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
				keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
				keyboardHold( settings.hotkeys.MOVE_FORWARD.key );
				turning = false
			else
				keyboardHold( settings.hotkeys.MOVE_FORWARD.key );
			end
		end

		--keyboardHold( settings.hotkeys.MOVE_FORWARD.key );
		local pausetime = loopduration - (os.clock() - loopstart) -- minus the time already elapsed.
		if pausetime < 1 then pausetime = 1 end
		yrest(pausetime);
		self:updateXYZ()
		waypoint:update();

	until false

	if (settings.profile.options.WP_NO_STOP ~= false) then
		if (dontStopAtEnd ~= true) or (settings.profile.options.QUICK_TURN == false) then
			keyboardRelease( settings.hotkeys.MOVE_FORWARD.key );
		end
	else
		keyboardRelease( settings.hotkeys.MOVE_FORWARD.key );
	end

	keyboardRelease( settings.hotkeys.ROTATE_LEFT.key );
	keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );

	return success, failreason;
end

function CPlayer:moveInRange(target, range, ignoreCycleTargets)
	return self:moveTo(target, ignoreCycleTargets, nil, range)
end

function CPlayer:waitForAggro()
	local startTime = os.time();

	self:updateBattling()
	self:updateTargetPtr()
	while( self.Battling and self.TargetPtr == 0 ) do
		yrest(100);
		self:updateBattling()
		self:updateTargetPtr()

		if( os.difftime(os.time(), startTime) > 5 ) then
			-- Wait no more than 5 seconds
			break;
		end
	end

	if( self.TargetPtr ) then
		self:fight();
	end
end

-- Forces the player to face a direction.
-- 'dir' should be in radians
function CPlayer:faceDirection(dir,diry)
	local Vec3 = 0
	if diry then
		Vec3 = math.sin(diry);
	else
		Vec3 = memoryReadRepeat("float", getProc(), self.Address + addresses.pawnDirYUVec_offset);
	end
	local hypotenuse = (1 - Vec3^2)^.5
	local Vec1 = math.cos(dir) * hypotenuse;
	local Vec2 = math.sin(dir) * hypotenuse;

	self.Direction = math.atan2(Vec2, Vec1);
	self.DirectionY = math.atan2(Vec3, (Vec1^2 + Vec2^2)^.5 );

	local tmpMountAddress = memoryReadRepeat("uint", getProc(), self.Address + addresses.charPtrMounted_offset);
	self:updateMounted()
	if self.Mounted and tmpMountAddress and tmpMountAddress ~= 0 then
		memoryWriteFloat(getProc(), tmpMountAddress + addresses.pawnDirXUVec_offset, Vec1);
		memoryWriteFloat(getProc(), tmpMountAddress + addresses.pawnDirZUVec_offset, Vec2);
		memoryWriteFloat(getProc(), tmpMountAddress + addresses.pawnDirYUVec_offset, Vec3);
	else
		memoryWriteFloat(getProc(), self.Address + addresses.pawnDirXUVec_offset, Vec1);
		memoryWriteFloat(getProc(), self.Address + addresses.pawnDirZUVec_offset, Vec2);
		memoryWriteFloat(getProc(), self.Address + addresses.pawnDirYUVec_offset, Vec3);
	end
end

-- turns the player at a given angel in grad
function CPlayer:turnDirection(_angle)
-- negative values means 'turn' left
-- positive values means 'turn' right
-- self.Direction values are from 0 til Pi and -Pi til 0 / 0 is at East

	local hf_new_direction;
	if( _angle < 0 ) then
		hf_new_direction = self.Direction-math.rad(_angle);	-- neg angle value result in neg rad values
	elseif ( _angle > 0 ) then
		hf_new_direction = self.Direction-math.rad(_angle);
	else
		hf_new_direction = self.Direction;
	end;

	if(hf_new_direction > math.pi) then 		-- values gt 3,14
		hf_new_direction = hf_new_direction - 2* math.pi;
	elseif (hf_new_direction < -math.pi) then 	-- value lt -3,14
		hf_new_direction = hf_new_direction + 2* math.pi;
	end

	self:faceDirection(hf_new_direction);	-- turn character
	camera:setRotation(hf_new_direction);	-- turn camera view behind character
end


-- Attempt to unstick the player
function CPlayer:unstick()
-- after 2x unsuccesfull unsticks try to reach last waypoint
	if( self.Unstick_counter == 3 ) then
	if unStick3 then
  			unStick3()
	elseif( self.Returning ) then
			__RPL:backward();
		else
			__WPL:backward();
		end;
		return;

	end


-- after 5x unsuccesfull unsticks try to reach next waypoint after sticky one
	if( self.Unstick_counter == 6 ) then
	if unStick6 then
  			unStick6()
	elseif( self.Returning ) then
			__RPL:advance();	-- forward to sticky wp
			__RPL:advance();	-- and one more
		else
			__WPL:advance();	-- forward to sticky wp
			__WPL:advance();	-- and one more
		end;
		return;

	end


-- after 8x unstick try to run away a little and then go to the nearest waypoint
	if( self.Unstick_counter == 9 ) then
		if unStick9 then
				unStick9()
		else
			-- turn and move back for 10 seconds
			keyboardHold(settings.hotkeys.ROTATE_RIGHT.key);
			yrest(1900);
			keyboardRelease( settings.hotkeys.ROTATE_RIGHT.key );
			keyboardHold(settings.hotkeys.MOVE_FORWARD.key);
			yrest(10000);
			keyboardRelease(settings.hotkeys.MOVE_FORWARD.key);
			self:updateXYZ();
			if( self.Returning ) then
				__RPL:setWaypointIndex(__RPL:getNearestWaypoint(self.X, self.Z));
			else
				__WPL:setWaypointIndex(__WPL:getNearestWaypoint(self.X, self.Z));
			end;
			return;
		end;
	end

 	-- Move back for x seconds
	keyboardHold(settings.hotkeys.MOVE_BACKWARD.key);
	yrest(1000);
	keyboardRelease(settings.hotkeys.MOVE_BACKWARD.key);

	-- Straff either left or right now
	local straffkey = 0;
	if( math.random(100) < 50 ) then
		straffkey = settings.hotkeys.STRAFF_LEFT.key;
	else
		straffkey = settings.hotkeys.STRAFF_RIGHT.key;
	end

	local straff_bonus = self.Unstick_counter * 120;
	keyboardHold(straffkey);
	yrest(500 + math.random(500) + straff_bonus);
	keyboardRelease(straffkey);

	-- try to jump over a obstacle
	if( self.Unstick_counter > 1 ) then
		if( self.Unstick_counter == 2 ) then
			keyboardHold(settings.hotkeys.MOVE_FORWARD.key);
			yrest(550);
			keyboardPress(settings.hotkeys.JUMP.key);
			yrest(400);
			keyboardRelease(settings.hotkeys.MOVE_FORWARD.key);
		elseif( math.random(100) < 80 ) then
			keyboardHold(settings.hotkeys.MOVE_FORWARD.key);
			yrest(600);
			keyboardPress(settings.hotkeys.JUMP.key);
			yrest(400);
			keyboardRelease(settings.hotkeys.MOVE_FORWARD.key);
		end;
	end;

end

function CPlayer:haveTarget()
	self:updateTargetPtr()
	local target = CPawn.new(self.TargetPtr);
	if target:exists() then
		return evalTargetDefault(target.Address,target)
	else
		return false
	end
end

function CPlayer:clearTarget()
	cprintf(cli.green, language[33]);
	memoryWriteInt(getProc(), self.Address + addresses.pawnTargetPtr_offset, 0);
	self.TargetPtr = 0;
	self.Cast_to_target = 0;

	RoMCode("TargetFrame:Hide()");
end

-- returns true if target is in mobs
function CPlayer:isInMobs(pawn)
	if( not pawn ) then
		error("CPlayer:isInMobs() received nil\n", 2);
	end;

	if not pawn:exists() then
		return false
	end

	pawn:updateName()
	for i,v in pairs(settings.profile.mobs) do
		if( string.find( string.lower(pawn.Name), string.lower(v), 1, true) ) or tonumber(v) == pawn.Id then
			return true;
		end
	end

	return false;
end


function CPlayer:logoutCheck()
-- timed logout check

	self:updateBattling()
	if(self.Battling == true) then
		return;
	end;

	if( settings.profile.options.LOGOUT_TIME > 0 ) then
		local elapsed = os.difftime(os.time(), self.BotStartTime);

		if( elapsed >= settings.profile.options.LOGOUT_TIME * 60 ) then
			cprintf(cli.yellow, language[53], math.ceil(elapsed/60), settings.profile.options.LOGOUT_TIME );	-- Logout elapsed > planned
			self:logout();
		end
	end
end

function CPlayer:logout(fc_shutdown, logout_close)
-- importing:
--   fc_shutdown true/false/nil
--   if nil, profile option 'settings.profile.options.LOGOUT_SHUTDOWN'
--   will decide if shutdown or not occurs

-- If logout_close is 'true' it will close the client
--   otherwise it will just logout to character selection.

	cprintf(cli.yellow, language[50], os.date() );	-- Logout at %time%

	if( fc_shutdown == nil  and  settings.profile.options.LOGOUT_SHUTDOWN == true ) then
		fc_shutdown = true;
	end;

	if logout_close == true then
		local PID = findProcessByWindow(getWin()); -- Get process ID
		os.execute("TASKKILL /PID " .. PID .. " /F");
		while(true) do
			-- Wait for process to close...
			if( findProcessByWindow(__WIN) ~= PID ) then
				printf(language[88]);
				break;
			end;
			yrest(100);
		end
	else
		-- Close all windows to avoid problems when relogging.
		RoMCode("CloseAllWindows()") yrest(500)

		RoMCode("Logout();");
		yrest(30000); -- Wait for the log out to process
	end

	if( fc_shutdown ) then
		cprintf(cli.yellow, language[51]);
		os.execute("\"%windir%\\system32\\shutdown.exe -s -t 30\" "); --Shutdown in 30 seconds.
	end

	error("Exiting: Auto-logout", 0); -- Not really an error, but it will drop us back to shell.

end

function CPlayer:check_aggro_before_cast(_jump, _skill_type)
-- break cast in last moment
-- works also if target is not visible and we get aggro from another mob
-- _jump = true       abort cast with jump hotkey

	local target = CPawn.new(self.TargetPtr)

	-- don't break if no target or self targeting
	if( not target:exists()) or
	   self.TargetPtr == self.Address then
		return false;
	end

	self:updateBattling();
	if( self.Battling == false )  then		-- no aggro
		return false;
	end;

	-- don't break friendly skills
	if( _skill_type ~= STYPE_DAMAGE and
	    _skill_type ~= STYPE_DOT ) then
		return false;
	end

	-- Don't break if we already started damaging target
	target:updateLastHP()
	if target.LastHP > 0 then
		return false;
	end

	-- Don't break if target is targeting us
	if target.TargetPtr == self.Address or
	   target:targetIsFriend() then
		return false;
	end

	-- Don't break if cast is nearly finished as it will probably still cast.
	self:updateCasting()
	if self.Casting and
	   self:getRemainingCastTime() <= getSkillUsePrior() then
		return false
	end

	-- target is alive and not attacking us
	if self.Cast_to_target == 0 then
		cprintf(cli.green, language[36], target.Name);	-- Aggro during first strike/cast
	end

	-- try fo find the aggressore a little faster by targeting it itself instead of waiting from the client
	local target = self:findEnemy(true,nil,evalTargetDefault)
	if target and target.Address ~= self.TargetPtr then	-- we found a new target
		self:target(target)
		cprintf(cli.green, "%s is attacking us, we take that target.\n", target.Name);	-- attacking us
	else
		return false
	end

	return true;
end

-- find a target with the ingame target key
-- is used while moving and could also used before moving or after fight
function CPlayer:findTarget()

	-- check if automatic targeting is active
	if( settings.profile.options.AUTO_TARGET == false ) then
		return false;
	end

	keyboardPress(settings.hotkeys.TARGET.key, settings.hotkeys.TARGET.modifier);

	yrest(10);

	-- We've got a target, fight it instead of worrying about our waypoint.

	if( self:haveTarget() ) then
	-- all other checks are within the self:haveTarget(), so the target should be ok

		local target = self:getTarget();
		local dist = distance(self.X, self.Z, target.X, target.Z);
		cprintf(cli.green, language[37], target.Name, dist);	-- Select new target %s in distance

		return true;
	else
		return false;
	end;

end


function CPlayer:rest(_restmin, _restmax, _resttype, _restaddrnd)
-- rest to restore mana and healthpoint if under a certain level
-- this function could also be used, if you want to rest in a waypoint file, it will
-- detect aggro while resting and fight back
--
--  player:rest( _restfix,[ _restrnd[, time|full[, _restaddrnd]]])
--
-- _restmin  ( minimum rest time in sec)
-- _restmax  ( maximum rest time sec)
-- _resttype ( time / full )  time = rest the given time  full = stop resting after being full   default = time
-- _restaddrnd  ( max random addition after being full in sec)
--
--
-- e.g.
-- player:rest(20)                 will rest for 20 seconds.
-- player:rest(60, 80)             will rest between 60 and 80 seconds.
-- player:rest(90, 130, "full")     will rest up to between 90 and 130 seconds, and stop resting
--                                 if being full
-- player:rest(20, 60, "full, 20") will rest up to between 20 and 60 seconds, and stop resting
--                                 if being full, and wait after that between 1-20 seconds
--
-- to look not so bottish, please use the random time options!!!
--
	self:updateBattling();
	if( self.Battling == true) then return; end;		-- if aggro, go back

	-- Stop if moving
	keyboardRelease( settings.hotkeys.MOVE_FORWARD.key );

	local hf_resttime;

	-- setting default values
	if( _restmin     == nil  or  _restmin  == 0 )   then _restmin     = 10; end;
	if( _restmax     == nil  or  _restmax  == 0 )   then _restmax     = _restmin; end;
	if( _resttype    == nil )                       then _resttype    = "time"; end;	-- default restype is 'time"

	if ( _restmax >  _restmin ) then
		hf_resttime = _restmin + math.random( _restmax - _restmin );
	else
		hf_resttime = _restmin;
	end;

	if( _restaddrnd  == nil  or  _restaddrnd == 0 ) then _restaddrnd  = 0;
	else _restaddrnd  = math.random( _restaddrnd ); end;

	-- some classes dont have mana, in that cases Player.mana = 0
	self:updateHP();
	self:updateMP();
	local hf_mana_rest = (self.MaxMana * settings.profile.options.MP_REST / 100);	-- rest if mana is lower then
	local hf_hp_rest   = (self.MaxHP   * settings.profile.options.HP_REST / 100);	-- rest if HP is lower then

	local restStart = os.time();		-- set start timer

	if( _resttype == "full") then
		cprintf(cli.green, language[38], ( hf_resttime ) );		-- Resting up to %s to fill up mana and HP
	else
		cprintf(cli.green, language[71], ( hf_resttime ) );		-- Resting for %s seconds.
	end;

	-- check before we perhaps sit down
	self:checkPotions();
	self:checkSkills( ONLY_FRIENDLY ); 		-- only cast friendly spells to ourselfe

	-- sit option is false as default and the option is not promoted
	-- simply because if you misconfigure the rest option / don't use potions you will
	-- rest and sit after every fight and that looks really bottish
	local we_sit = false;
	if( _resttype == "full" and			-- only sit for full rest, not for times restings
		settings.profile.options.SIT_WHILE_RESTING ) then
		RoMCode("SitOrStand()");
		we_sit = true;
	end;

	while ( true ) do

		self:updateBattling();
		if( self.Battling ) then          -- we get aggro,
			self:clearTarget();      -- get rid of mob to be able to target attackers
			cprintf(cli.green, language[39] );   -- get aggro
			break;
		end;

		-- check if resttime finished
		if( os.difftime(os.time(), restStart ) > ( hf_resttime ) ) then
			cprintf(cli.green, language[70], ( hf_resttime ) );   -- Resting finished after %s seconds
			break;
		end;

		-- check if HP/Mana full
		self:updateHP();
		self:updateMP();
		if( self.Mana == self.MaxMana  and		-- some chars have MaxMana = 0
 	 	    self.HP   == self.MaxHP    and
 	 	    _resttype   == "full" ) then		-- Mana and HP are full
			local restAddStart = os.time();		-- set additional rest timer
			while ( true ) do	-- rnd addition
				if( os.difftime(os.time(), restAddStart ) > _restaddrnd ) then
					break;
				end;
				self:updateBattling();
				if( self.Battling ) then          -- we get aggro,
					self:clearTarget();      -- get rid of mob to be able to target attackers
					cprintf(cli.green, language[39] );   -- Stop resting because of aggro
					break;
				end;
				self:checkPotions();
				self:checkSkills( ONLY_FRIENDLY ); 		-- only cast friendly spells to ourselfe
				yrest(100);
			end;

			cprintf(cli.green, language[70], os.difftime(os.time(), restStart ) );   -- full after x sec
			break;
		end;

		if( we_sit == false) then		-- can't cast while sitting
			self:checkPotions();
			self:checkSkills( ONLY_FRIENDLY ); 		-- only cast friendly spells to ourselfe
		end

		yrest(100);

	end;			-- end of while

	if( we_sit == true  and
		not self.Battling ) then	-- if aggro the char will standup automaticly
		RoMCode("SitOrStand()");
		yrest(1500);					-- give time to standup
	end;

end

function CPlayer:restrnd(_probability, _restmin, _restmax)
-- call the rest function with a given probability

	if( math.random( 100 ) < _probability ) then
		self:rest(_restmin, _restmax, "time", 0 )
	end;

end

function CPlayer:sleep(duration)
-- the bot will sleep but still fight back attackers

	local sleep_start = os.time();		-- calculate the sleep time
	self.Sleeping = true;	-- we are sleeping

	cprintf(cli.yellow, language[89], os.date(), getKeyName(getStartKey())  );

	local hf_key = "";
	while(true) do

		local hf_key_pressed = false;

		if( keyPressedLocal(getStartKey()) ) then	-- start key pressed
			hf_key_pressed = true;
			hf_key = "AWAKE";
		end;

		if( hf_key_pressed == false ) then	-- key released, do the work

			-- START Key: wake up
			if( hf_key == "AWAKE" ) then
				hf_key = " ";	-- clear last pressed key

				cprintf(cli.yellow, language[90], getKeyName(getStartKey()),  os.date() );
				self.Sleeping = false;	-- we are awake
				break;
			end;

			hf_key = " ";	-- clear last pressed key
		end;

		if duration and os.time()-sleep_start > duration then
			self.Sleeping = false;	-- we are awake
			break;
		end;

		self:updateBattling();
		-- wake up if aggro, but we don't clear the sleeping flag
		if( self.Battling ) then          -- we get aggro,
			self:clearTarget();       -- get rid of mob to be able to target attackers
			cprintf(cli.yellow, language[91], os.date() );  -- Awake from sleep because of aggro
			break;
		end;

		yrest(10);
		self:logoutCheck(); 		-- check logout timer
	end					-- end of while

	-- count the sleeping time
	self.Sleeping_time = self.Sleeping_time + os.difftime(os.time(), sleep_start);

end

function CPlayer:scan_for_NPC(_npcname)

	local msg = "Function player:scan_for_NPC() is no longer available. Use function player:target_NPC(_npcname) instead. That function will also work in background mode.";
	error(msg, 0);

	if( foregroundWindow() ~= getWin() ) then
		return;
	end

	local function scan()
		local mousePawn;
		-- Screen dimension variables
		local wx, wy, ww, wh = windowRect(getWin());
		local halfWidth = ww/2;
		local halfHeight = wh/2;

		-- Scan rect variables
		local scanWidth = settings.profile.options.HARVEST_SCAN_WIDTH; -- Width, in 'steps', of the area to scan
		local scanHeight = settings.profile.options.HARVEST_SCAN_HEIGHT; -- Height, in 'steps', of area to scan
		local scanXMultiplier = settings.profile.options.HARVEST_SCAN_XMULTIPLIER;	-- multiplier for scan width
		local scanYMultiplier = settings.profile.options.HARVEST_SCAN_YMULTIPLIER;	-- multiplier for scan line height
		local scanStepSize = settings.profile.options.HARVEST_SCAN_STEPSIZE; -- Distance, in pixels, between 'steps'

		local mx, my; -- Mouse x/y temp values

		mouseSet(wx + (halfWidth*scanXMultiplier - (scanWidth/2*scanStepSize)),
		wy  + (halfHeight*scanYMultiplier - (scanHeight/2*scanStepSize)));
		yrest(200);

		local scanstart, scanende, scanstep;
		-- define scan direction top/down  or   bottom/up
		if( settings.profile.options.HARVEST_SCAN_TOPDOWN == true ) then
			scanstart = 0;
			scanende = scanHeight-1;
			scanstep = 1;
		else
			scanstart = scanHeight;
			scanende = 0;
			scanstep = -1;
		end;

		-- Scan nearby area for a node
		keyboardHold(key.VK_SHIFT);	-- press shift so you can scan trough players
		for y = scanstart, scanende, scanstep do
			my = math.ceil(halfHeight * scanYMultiplier - (scanHeight / 2 * scanStepSize) + ( y * scanStepSize ));

			for x = 0,scanWidth-1 do
				mx = math.ceil(halfWidth * scanXMultiplier - (scanWidth / 2 * scanStepSize) + ( x * scanStepSize ));

				mouseSet(wx + mx, wy + my);
				yrest(settings.profile.options.HARVEST_SCAN_YREST+3);
				mousePawn = CPawn(memoryReadRepeat("uintptr", getProc(),
				addresses.staticbase_char, addresses.mousePtr_offset));

				-- id 110504 Waffenhersteller Dimar
				-- id 110502 Dan (Gemischtwarenhï¿½ndler
				-- id 1000, 1001 Player
				if( mousePawn.Address ~= 0 and mousePawn.Type == PT_NPC
					and distance(self.X, self.Z, mousePawn.X, mousePawn.Z) < 150
  					and mousePawn.Id > 100000 ) then
					local target = CPawn(mousePawn.Address);
					if( _npcname and			-- check ncp name
					    not string.find(target.Name, _npcname ) ) then
					    local dummy = 1;			-- do nothing
					else
						cprintf(cli.green, "We found NPC: %s\n", target.Name);
						return mousePawn.Address, mx, my;
					end;
				end
			end
		end
		keyboardRelease(key.VK_SHIFT);


		return 0, nil, nil;
	end


	detach(); -- Remove attach bindings
	local mouseOrigX, mouseOrigY = mouseGetPos();
	local foundHarvestNode, nodeMouseX, nodeMouseY = scan();

	if( foundHarvestNode ~= 0 and nodeMouseX and nodeMouseY ) then

		-- If out of distance, move and rescan
		local mousePawn = CPawn(foundHarvestNode);
		local dist = distance(self.X, self.Z, mousePawn.X, mousePawn.Z)

		if( dist > 35 and dist < 150 ) then
			printf(language[80]);	-- Move in
			self:moveTo( CWaypoint(mousePawn.X, mousePawn.Z), true );
			yrest(200);
			foundHarvestNode, nodeMouseX, nodeMouseY = scan();
		end

		self:update();

		local wx,wy = windowRect(getWin());
		--mouseSet(wx + nodeMouseX, wy + nodeMouseY);
		mouseSet(wx + nodeMouseX, wy + nodeMouseY);
		yrest(3000);		-- wait for zoom in / out movement bug

		-- click NPC
		keyboardHold(key.VK_SHIFT);
		mouseLClick();		-- one click to target npc
		yrest(500);
		mouseLClick();		-- one click to open dialog
		yrest(500);
		mouseLClick();		-- one click to be really sure
		keyboardRelease(key.VK_SHIFT);

		self:update();

		yrest(2000);

	end

	mouseSet(mouseOrigX, mouseOrigY);
	attach(getWin()); -- Re-attach bindings
end

function CPlayer:mouseclick(_x, _y, _wwide, _whigh, _type)
	if( foregroundWindow() ~= getWin() ) then
		cprintf(cli.yellow, language[139]);	-- RoM window has to be in the foreground
		return;
	end

	detach(); -- Remove attach bindings

	local wx,wy,wwide,whigh  = windowRect(getWin());
	local hf_x, hf_y;

	-- recalulate clickpoints depending from the actual RoM windows size
	-- only if we know the original windows size from the clickpoints
	if(_wwide  and _whigh) then
		hf_x = wwide * _x / _wwide;
		hf_y = whigh * _y / _whigh;
		cprintf(cli.green, language[92], -- Mouseclick Left at %d,%d in %dx%d (recalculated
			_type, hf_x, hf_y, wwide, whigh, _x, _y, _wwide, _whigh);
	else
		hf_x = _x;
		hf_y = _y;
		cprintf(cli.green, language[93],	 -- Clicking mouseL at %d,%d in %dx%d\n
			_type, hf_x, hf_y, wwide, whigh);
	end;

	mouseSet(wx + hf_x, wy + hf_y);
	yrest(100);

	if( string.lower(_type) == "l"  or  string.lower(_type) == "left" ) then
		mouseLClick();
	elseif( string.lower(_type) == "r"  or  string.lower(_type) == "right" ) then
		mouseRClick();
	elseif( string.lower(_type) == "m"  or  string.lower(_type) == "middle" ) then
		mouseMClick();
	else
		cprintf(cli.yellow, "Unknow option %s for function CPlayer:mouseclick()\n", _type);
	end
	yrest(100);

	attach(getWin()); -- Re-attach bindings
end

function CPlayer:mouseclickL(_x, _y, _wwide, _whigh)
	self:mouseclick(_x, _y, _wwide, _whigh, "left")
end

function CPlayer:mouseclickR(_x, _y, _wwide, _whigh)
	self:mouseclick(_x, _y, _wwide, _whigh, "right")
end

function CPlayer:mouseclickM(_x, _y, _wwide, _whigh)
	self:mouseclick(_x, _y, _wwide, _whigh, "middle")
end

-- open an npc store
function CPlayer:openStore(_npcname, _option)
	_option = _option or 1
	if self:target_NPC(_npcname) then
		yrest(1000);
		RoMCode("ChoiceOption(".._option..")");
		yrest(1000);
		if RoMScript("StoreFrame:IsVisible()") then
			return true
		end
	end

	return false
end

-- auto interact with a merchant
function CPlayer:merchant(_npcname, _option, _evalfunc)
	if self:openStore(_npcname, _option) then
		RoMCode("ClickRepairAllButton()");
		yrest(1000);

		inventory:update();
		if ( inventory:autoSell(_evalfunc) ) then
			inventory:update();
		end
		store:buyConsumable("hot", settings.profile.options.HEALING_POTION);
		store:buyConsumable("mot", settings.profile.options.MANA_POTION);
		store:buyConsumable("arrow_quiver", settings.profile.options.ARROW_QUIVER);
		store:buyConsumable("thrown_bag", settings.profile.options.THROWN_BAG);
		store:buyConsumable("poison", settings.profile.options.POISON);
		if settings.profile.options.EGGPET_ENABLE_CRAFT then
			if settings.profile.options.EGGPET_CRAFT_RATIO then
				local mRatio, wRatio, hRatio = string.match(settings.profile.options.EGGPET_CRAFT_RATIO,"(%d*)%s*:%s*(%d*)%s*:%s*(%d*)")
				if tonumber(mRatio) > 0 then
					store:buyConsumable("eggpet_hoe", settings.profile.options.EGGPET_HOE);
				end
				if tonumber(wRatio) > 0 then
					store:buyConsumable("eggpet_hatchet", settings.profile.options.EGGPET_HATCHET);
				end
				if tonumber(hRatio) > 0 then
					store:buyConsumable("eggpet_spade", settings.profile.options.EGGPET_SPADE);
				end
			else
				store:buyConsumable("eggpet_hoe", settings.profile.options.EGGPET_HOE);
				store:buyConsumable("eggpet_hatchet", settings.profile.options.EGGPET_HATCHET);
				store:buyConsumable("eggpet_spade", settings.profile.options.EGGPET_SPADE);
			end
		end
		inventory:update();
	end

	RoMCode("CloseWindows()");

end

-- trys to target a friendly NPC/player with the ingame targetnearestfriend key
-- if after some tries we don't find the target, the character will turn around
-- in steps and tries again to find the target
function CPlayer:target_NPC(_npcname)
	if( not _npcname ) then
		cprintf(cli.yellow, language[133]);	-- Please give a NPC name
		return
	end

	cprintf(cli.green, language[135], _npcname);	-- We try to find NPC

	if type(_npcname) == "string" and
		bot.ClientLanguage == "RU" then
		_npcname = utf82oem_russian(_npcname);		-- language conversations for Russian Client
	end

	local npc = self:findNearestNameOrId(_npcname)
	if npc then	-- we successfully found NPC
		cprintf(cli.green, language[136], npc.Name);	-- we successfully target NPC
		if( distance(self.X, self.Z, npc.X, npc.Z) > 39 ) then
			self:moveInRange(CWaypoint(npc.X, npc.Z), 39, true);
		end
		-- target NPC
		self:target(npc.Address)
		Attack(); yrest(50); Attack(); -- 'click' again to be sure
		yrest(500);
		return true
	else
		cprintf(cli.green, language[137], _npcname);	-- we can't find NPC
		return false
	end
end

function CPlayer:findNearestNameOrId(_objtable, ignore, evalFunc)
	if type(_objtable) == "number" or type(_objtable) == "string" then
		_objtable = {_objtable}
	end
	local foundobjects = {}
	ignore = ignore or 0;
	local closestObject = nil;
	local obj = nil;
	local objectList = CObjectList();
	objectList:update();

	if( type(evalFunc) ~= "function" ) then
		evalFunc = function (unused) return true; end;
	end

	self:updateXYZ()
	for i = 0,objectList:size() do
		obj = objectList:getObject(i);

		if( obj ~= nil ) then
			for __, _objnameorid in pairs(_objtable) do
				if( obj.Address ~= ignore and obj.Address ~= self.Address and (obj.Id == tonumber(_objnameorid) or string.find(obj.Name, _objnameorid, 1, true) )) then
					if( evalFunc(obj.Address,obj) == true ) then
						obj.Distance = distance(obj,self)
						table.insert(foundobjects,obj)
					end
				end
			end
		end
	end
	-- sort by distance
	local function distancesortfunc(a,b)
		return b.Distance > a.Distance
	end
	if #foundobjects ~= 0 then -- sort according to distance first
		table.sort(foundobjects, distancesortfunc)
		return foundobjects[1], foundobjects -- return closest object, return all objects found
	end
	return -- means you found nothing, so returns nil
end

function CPlayer:target_Object(_objname, _waittime, _harvestall, _donotignore, evalFunc)
	_waittime = _waittime or 0
	local minWaitTime = 1000 -- minimum time to wait for castbar to come up.
	_harvestall = (_harvestall == true)
	if type(_donotignore) ~= "boolean" then _donotignore = (_harvestall == false) end -- default value depends on _harvestall
	if( not _objname ) then
		cprintf(cli.yellow, language[181]);	-- Please give an Object name
		return
	end

	-- Make sure we come to a stop before attempting to harvest.
	self:waitTillStopMoving()

 	local objFound = false;

	while(true) do
		repeat
			interrupted = false
			if _donotignore == false then
				obj = self:findNearestNameOrId(_objname, self.LastTargetPtr, evalFunc)
			else
				obj = self:findNearestNameOrId(_objname, nil, evalFunc)
			end

			-- Check if too far
			if obj and ( distance(self.X, self.Z, self.Y, obj.X, obj.Z, obj.Y ) > settings.profile.options.HARVEST_DISTANCE ) then
				obj = nil
			end

			if obj then -- object found, target
				if self.LastTargetPtr ~= obj.Address then
					cprintf(cli.yellow, language[95], obj.Name); -- We found object and will harvest it
					self.LastTargetPtr = obj.Address;		-- remember target address to avoid msg spam
				end
				objFound = true
				self:target(obj.Address);
				if( distance(self.X, self.Z, obj.X, obj.Z) > 39 ) then
					self:moveInRange(CWaypoint(obj.X, obj.Z), 39, true);
					repeat
						yrest(50)
						self:updateActualSpeed()
					until not self.Moving
				end
				yrest(100)
				Attack()
				local timeStart = getTime()

				--Wait minimum time
				repeat
					yrest(100)
					self:updateCasting()
					self:updateBattling()
					if self.Casting or self.Battling then break end
				until deltaTime(getTime(),timeStart) >= minWaitTime

				if self.Casting == false and _waittime > 0 then -- Was expecting castingbar so try again, 2nd try.
					Attack(); yrest(50);
					timeStart = getTime()
				end

				repeat
					yrest(100);
					self:updateBattling();
					while( self.Battling ) do
						if self:target(self:findEnemy(true, nil, evalTargetDefault)) then
							self:fight();
							interrupted = true
						else
							break
						end
						self:updateBattling();
					end
					self:updateCasting()
				until interrupted or (deltaTime(getTime(),timeStart) > _waittime and self.Casting == false)
			end
		until interrupted == false

		if obj then -- harvest again
			if _donotignore ~= true then
				self.LastTargetPtr = obj.Address -- Default ignore this address in next search
			end
			if _harvestall == false then -- No more harvesting
				return objFound
			end
		else
			return objFound
		end
	end
end

function CPlayer:mount(_dismount)
	self:updateMounted()
	if( (not _dismount) and self.Mounted ) then
		printf("Already mounted.\n");
		return;
	end

	if( _dismount and (not self.Mounted) ) then
		printf("Already dismounted.\n");
		return;
	end

	self:updateSwimming()
	if self.Swimming then
		printf("Swimming. Can't mount.\n")
		return
	end

	local mountMethod = false
	local mount

	-- Find mount
	if RoMScript("PartnerFrame_GetPartnerCount(2)") > 0 then
		-- There is a mount in the partner bag. Assign the mountmethod.
		mountMethod = "partner"
	elseif inventory then -- Make sure inventory has been mapped.
		mount = inventory:getMount();
		if mount then
			mountMethod = "inventory"
		end
	end

	-- Mount found?
	if(not mountMethod ) then
		return
	end

	-- Make sure we are not battling before trying to mount
	if not _dismount and not (self.Current_waypoint_type == WPT_TRAVEL) then
		self:updateBattling();
		while( self.Battling ) do
			if self:target(self:target(self:findEnemy(true, nil, evalTargetDefault))) then
				self:fight();
			else
				break
			end
			self:updateBattling();
		end
	end

	-- if _dismount and mountmethod is inventory then assume buff name equals item name and cancel buff if exists. Mainly needed for 15m and 2h mounts
	if _dismount and mountMethod == "inventory" then
		self:updateBuffs()
		for index, buff in pairs(self.Buffs) do
			if string.find(mount.Name,buff.Name,1, true) then
				sendMacro("CancelPlayerBuff("..index..");")
				return
			end
		end
	end

	-- Stop moving before trying to mount
	player:updateActualSpeed()
	if not _dismount and player.Moving then
		releaseKeys()
		repeat
			yrest(50)
			player:updateActualSpeed()
		until not player.Moving
	end

	-- mount/dismount
	if mountMethod == "partner" then
		RoMCode("PartnerFrame_CallPartner(2,1)")
	else
		mount:use()
	end
	yrest(500)

	repeat
		yrest(100);
		self:updateCasting();
	until self.Casting == false

	-- Just in case you mounted a different mount instead of dismounting
	self:updateMounted()
	if _dismount == true and self.Mounted then
		-- second try dismount
		yrest(1000)
		if mountMethod == "partner" then
			RoMCode("PartnerFrame_CallPartner(2,1)")
		else
			mount:use()
		end
	end
	yrest(500)
end

function CPlayer:dismount()
	self:mount(true)
end

function CPlayer:waitTillCastingEnds()
	local prior = getSkillUsePrior();
	self:updateCasting()
	while(self.Casting) do
		yrest(10);
		self:updateCasting();

		if self:getRemainingCastTime() <= prior/1000 then
			break;
		end
	end
end

function CPlayer:aimAt(target)
	if target.Address then
		if target.Level then
			target:updateXYZ() -- only update if a pawn
		else
			target:update() -- if it's an object
		end
	end

	camera:update()

	-- camera distance to camera focus
	local cameraDistance = distance(camera.XFocus,camera.ZFocus,camera.YFocus,camera.X,camera.Z,camera.Y)
	if cameraDistance > 150 then cameraDistance = 150 end

	-- Target distance to camera focus
	local targetDistance = distance(camera.XFocus,camera.ZFocus,camera.YFocus,target.X,target.Z,target.Y)

	-- Ratio
	local ratio = cameraDistance/targetDistance

	-- Vectors
	local vec1 = (camera.XFocus - target.X) * ratio
	local vec2 = (camera.ZFocus - target.Z) * ratio
	local vec3 = (camera.YFocus - (target.Y or camera.YFocus)) * ratio

	-- New Camera coordinates
	local nx = camera.XFocus + vec1
	local nz = camera.ZFocus + vec2
	local ny = camera.YFocus + vec3

	-- write camera coordinates
	memoryWriteFloat(getProc(), camera.Address + addresses.camX_offset, nx);
	memoryWriteFloat(getProc(), camera.Address + addresses.camZ_offset, nz);
	memoryWriteFloat(getProc(), camera.Address + addresses.camY_offset, ny);
end

function CPlayer:clickToCast()
	-- Freeze mouse function
	local function nopmouse()
		-- x axis
		local addressX1 = addresses.functionMousePatchAddr
		local addressX2 = addresses.functionMousePatchAddr + addresses.mousePatchX2_offset
		local addressX3 = addresses.functionMousePatchAddr + addresses.mousePatchX3_offset
		memoryWriteString(getProc(), addressX1, string.rep(string.char(0x90),#addresses.functionMouseX1Bytes)); -- left of window
		memoryWriteString(getProc(), addressX2, string.rep(string.char(0x90),#addresses.functionMouseX2Bytes)); -- right of window
		memoryWriteString(getProc(), addressX3, string.rep(string.char(0x90),#addresses.functionMouseX3Bytes)); -- over window

		-- y axis
		local addressY1 = addresses.functionMousePatchAddr + addresses.mousePatchY1_offset
		local addressY2 = addresses.functionMousePatchAddr + addresses.mousePatchY2_offset
		local addressY3 = addresses.functionMousePatchAddr + addresses.mousePatchY3_offset
		memoryWriteString(getProc(), addressY1, string.rep(string.char(0x90),#addresses.functionMouseY1Bytes)); -- above window
		memoryWriteString(getProc(), addressY2, string.rep(string.char(0x90),#addresses.functionMouseY2Bytes)); -- below window
		memoryWriteString(getProc(), addressY3, string.rep(string.char(0x90),#addresses.functionMouseY3Bytes)); -- over window
	end

	-- Unfreeze mouse function
	local function unnopmouse()
		-- x axis
		local addressX1 = addresses.functionMousePatchAddr
		local addressX2 = addresses.functionMousePatchAddr + addresses.mousePatchX2_offset
		local addressX3 = addresses.functionMousePatchAddr + addresses.mousePatchX3_offset
		memoryWriteString(getProc(), addressX1, string.char(unpack(addresses.functionMouseX1Bytes)));
		memoryWriteString(getProc(), addressX2, string.char(unpack(addresses.functionMouseX2Bytes)));
		memoryWriteString(getProc(), addressX3, string.char(unpack(addresses.functionMouseX3Bytes)));

		-- y axis
		local addressY1 = addresses.functionMousePatchAddr + addresses.mousePatchY1_offset
		local addressY2 = addresses.functionMousePatchAddr + addresses.mousePatchY2_offset
		local addressY3 = addresses.functionMousePatchAddr + addresses.mousePatchY3_offset
		memoryWriteString(getProc(), addressY1, string.char(unpack(addresses.functionMouseY1Bytes)));
		memoryWriteString(getProc(), addressY2, string.char(unpack(addresses.functionMouseY2Bytes)));
		memoryWriteString(getProc(), addressY3, string.char(unpack(addresses.functionMouseY3Bytes)));
	end

	local ww = memoryReadIntPtr(getProc(),addresses.staticbase_char,addresses.windowSizeX_offset)
	local wh = memoryReadIntPtr(getProc(),addresses.staticbase_char,addresses.windowSizeY_offset)
	local clickX = math.ceil(ww/2)
	local clickY = math.ceil(wh/2)
	yrest(50)
	nopmouse()
	yrest(50)
	memoryWriteIntPtr(getProc(),addresses.staticbase_char,addresses.mouseX_offset,clickX)
	memoryWriteIntPtr(getProc(),addresses.staticbase_char,addresses.mouseY_offset,clickY)
	yrest(50)
	RoMCode("SpellTargetUnit()")
	yrest(50)
	-- unfreeze TargetPtr
	unnopmouse()
end

function CPlayer:getCraftLevel(craft)
	if string.lower(craft) == "blacksmithing" then craft = CRAFT_BLACKSMITHING
	elseif string.lower(craft) == "carpentry" then craft = CRAFT_CARPENTRY
	elseif string.lower(craft) == "armorcrafting" then craft = CRAFT_ARMORCRAFTING
	elseif string.lower(craft) == "tailoring" then craft = CRAFT_TAILORING
	elseif string.lower(craft) == "cooking" then craft = CRAFT_COOKING
	elseif string.lower(craft) == "alchemy" then craft = CRAFT_ALCHEMY
	elseif string.lower(craft) == "mining" then craft = CRAFT_MINING
	elseif string.lower(craft) == "woodcutting" then craft = CRAFT_WOODCUTTING
	elseif string.lower(craft) == "herbalism" then craft = CRAFT_HERBALISM
	end

	if type(craft) ~= "number" or craft < 0 or craft > 8 then
		cprintf(cli.yellow, language[77])
		return
	end

	local lvl = memoryReadFloat(getProc(),addresses.playerCraftLevelBase + addresses.playerCraftLevel_offset + craft*4)

	return lvl
end

function CPlayer:waitTillStopMoving(maxtime)
	self:updateActualSpeed()
	if self.Moving then
		maxtime = maxtime or 400
		local starttime = getTime()
		repeat
			yrest(10)
			self:updateActualSpeed()
		until self.ActualSpeed == 0 or deltaTime(getTime(),starttime) > maxtime
	end
end

function CPlayer:addToMobIgnoreList(target)
	if type(target) == "table" then
		table.insert(self.MobIgnoreList,{Address=target.Address,Time=os.clock()})
	else
		table.insert(self.MobIgnoreList,{Address=target,Time=os.clock()})
	end
	self:updateXYZ()
	self.LastPlaceMobIgnored = {X=self.X,Z=self.Z,Y=self.Y}
end

function CPlayer:clearMobIgnoreList()
	self:updateXYZ()
	-- Only clear list if you have traveled 50 from last ignore
	if self.LastPlaceMobIgnored == nil or distance(self,self.LastPlaceMobIgnored) > 50 then
		self.MobIgnoreList = {}
	end
end

function CPlayer:getLastDamage()
	return RoMScript("igf_events:getLastPlayerDamage()")
end

function CPlayer:getLastBlockTime()
	return RoMScript("igf_events:getLastPlayerBlockTime()")
end

function CPlayer:getLastDodgeTime()
	return RoMScript("igf_events:getLastPlayerDodgeTime()")
end