-- R5Version 2.74

setExecutionPath(getExecutionPath().."/..")

bizarremechanism = 101489
guardianrockspirit = GetIdName(101269)
guardian = GetIdName(101270)
guardiandefender = GetIdName(101637)
guardianswordsman = GetIdName(101271)
franticcliffdemon = GetIdName(101272)
brownwoodenchest = 111956
smallbomb = 101486
mediumbomb = 101487
largebomb = 101488
treasuretile = GetIdName(111811)
joker = GetIdName(111814)

logentry = nil
numshells = getCurrency("shell")

function checkRelog()

	if logentry == "Ran out of time" and LastTileDug then
		if LastTileDug > 36 then LastTileDug = 36 end
		logentry = logentry .. string.format(" with %d tiles remaining.", 36 - LastTileDug)
	else
		logentry = logentry .. "."
	end

	-- Log result
	local filename = getExecutionPath() .. "/logs/survival.log";
	local file, err = io.open(filename, "a+");
	if file then
		file:write("Acc: "..RoMScript("GetAccountName()").."\tName: " ..string.format("%-10s",player.Name ).." \tDate: " .. os.date() ..
		" \tShells gained/total: "..getCurrency("shell") - numshells.."/".. getCurrency("shell").. "\t" ..logentry .. "\n")
		file:close();
	end

	if When_Finished == "relog" then
		ChangeChar()
		waitForLoadingScreen();
		yrest(3000)
		--loadProfile()
		loadPaths(__WPL.FileName);
	elseif When_Finished == "charlist" then
		SetCharList(CharList)
		LoginNextChar()
		--loadProfile()
		loadPaths(__WPL.FileName);
	elseif When_Finished == "end" then
		error("Ending script",2)
	elseif type (When_Finished)=='function' then
		When_Finished ()
	else
		--loadProfile() -- Because we messed with skills
		loadPaths(When_Finished)
	end
end

--=== Function to sort tables, at angle ===--
local function SEsize(_x, _z)
   local X1 = 2622.1403808594
   local Z1 = 2900.1105957031
   local X2 = 2471.7895507813
   local Z2 = 2954.833984375

   return math.floor(((_x-X1)*(Z2-Z1)-(X2-X1)*(_z-Z1))/math.sqrt((X2-X1)^2 + (Z2-Z1)^2) + 0.5)
end

local function NEsize(_x, _z)
   local X1 = 2471.7895507813
   local Z1 = 2954.833984375
   local X2 = 2526.5126953125
   local Z2 = 3105.1848144531

   return math.floor(((_x-X1)*(Z2-Z1)-(X2-X1)*(_z-Z1))/math.sqrt((X2-X1)^2 + (Z2-Z1)^2) + 0.5)
end

function addToNE(_x, _z, _s)
   return _x + (_s * 0.9397), _z - (_s * 0.3420)
end

function addToSE(_x, _z, _s)
   return _x + (_s * 0.3420), _z + (_s * 0.9397)
end

function GetAttackPosition(_x, _z)
   local SEvalue = SEsize(_x, _z)
   local NEvalue = NEsize(_x, _z)

	if 1 > SEvalue then -- nw edge
		_x, _z = addToSE(_x, _z, - attackdistance)
	elseif SEvalue > 159 then -- se edge
		_x, _z = addToSE(_x, _z, attackdistance)
	end

	if 1 > NEvalue then -- sw edge
		_x, _z = addToNE(_x, _z, - attackdistance)
	elseif NEvalue > 159 then -- ne edge
		_x, _z = addToNE(_x, _z, attackdistance)
	end

	return _x, _z
end

local tiles
--=== Create table of tiles ===--
function createTileTable()
	repeat
		tiles = {}

		local objectList = CObjectList();
		objectList:update();
		local objSize = objectList:size()

		for i = 0,objSize do
			local obj = objectList:getObject(i);
			if obj.Id == 111811 or obj.Id == 111812 then
				table.insert(tiles, table.copy(obj))
			end
		end

		-- sort by address
		local function addresssortfunc(a,b)
			return a.Address > b.Address
		end
		table.sort(tiles, addresssortfunc)

		-- check for duplicate addresses
		for i = #tiles - 1, 1, -1 do
			if tiles[i].Address == tiles[i+1].Address then
				print("Diplicate found. Removing.\a")
				table.remove(tiles,i)
			end
		end

		if #tiles ~= 36 then
			printf("#tiles = "..#tiles..". Redoing table.\n")
			yrest(500)
		end
	until #tiles == 36

	-- Sort function
	local function sortfunc(a,b)
		if SEsize(a.X,a.Z) == SEsize(b.X,b.Z) then
			return NEsize(b.X,b.Z) > NEsize(a.X,a.Z)
		else
			return SEsize(b.X,b.Z) > SEsize(a.X,a.Z)
		end
	end

   -- Sort tiles
   table.sort(tiles, sortfunc)
end

--=== look for indicator that the tile is clickable ===--
function clicktile(address)
	local tmp = memoryReadRepeat("int", getProc(), address + addresses.pawnAttackable_offset) or 0;
	if bitAnd(tmp,0x8) then
		return true
	else
		return false
	end
end

--=== look for indicator that the chest has loot ===--
function clickchest(address)
	local chest = memoryReadRepeat("byte", getProc(), address + 0x2F0) or 0
	if chest ~= 0 then
		return true
	else
		return false
	end
end

--=== custom fight function ===--
function myfight(target)
	target = CPawn(target.Address)

	-- Kill any aggro mobs before going back to target
	player:update()
	if player.Battling then
		repeat
			yrest(50)
			local _enemy = findaggroenemy()
			if _enemy then
				player:target(_enemy);
				player:fight()
			end
		until _enemy == nil or player.HP < 1 or player.Alive == false
	end

	-- Attack target
	target:update()
	player:aimAt(target)
	while target.Id ~= 0 and target.HP > 4 and target:isAlive() do
		yrest(50)
		player:target(target)
		if aoefound == false and (player.Class1 == CLASS_WARRIOR or player.Class1 == CLASS_CHAMPION) then Attack() end
		-- Check skills
		for i,v in pairs(settings.profile.skills) do
			if v.AutoUse and v:canUse(false, target) and
			   (v.Type == STYPE_DOT or v.Type == STYPE_DAMAGE) and
			   AllowSkillCastTime >= v.CastTime then
				local oldHP = target.HP -- Remember HP for extra check
				player:cast(v) -- Cast attack skill
				repeat yrest(50) player:update() until not player.Casting
				player:checkSkills(true) -- Friendly skills only.
				-- Wait a bit of time for damage to register, avoids casting another skill needlessly.
				local starttime = os.clock()
				yrest(150)
				repeat
					yrest(50)
					target:update()
				until target.Id == 0 or 5 > target.HP or target.HP < oldHP or aoefound or (os.clock()-starttime) >= 1
				-- End checking skills if target dead or if using AOE
				local damage = memoryReadInt(getProc(), target.Address + 0x1D0)
				if target.Id == 0 or 5 > target.HP or damage == oldHP or aoefound then
					break
				end
			end
		end
		target:update()
		player:update()
		-- player dead?
		if (player.HP < 1 or player.Alive == false) then break end
		player:checkPotions()
	end
	player:resetSkills()
end

local function evalfunc(addr)
	local pawn = CPawn(addr)
	if pawn.HP > 4 and pawn:isAlive() then
		if aoefound then
			return attackrange > distance(player.X, player.Z, pawn.X, pawn.Z)
		else
			return attackdistance*1.5 > distance(player.X, player.Z, pawn.X, pawn.Z)
		end
	end
end

--=== First mobs to kill ===--
function trashmelee()
	cprintf(cli.yellow,"Clearing trash...\n")
	while (player:findNearestNameOrId({bizarremechanism,guardianrockspirit},nil,function(addr) local pawn = CPawn(addr) return pawn.HP > 4 and pawn:isAlive() end)) do
		for i = 1,#orderlist do
			if not RoMScript("TimeKeeperFrame:IsVisible()") then
				return
			end
			local tile = tiles[orderlist[i]]
			local x,z = GetAttackPosition(tile.X,tile.Z)
			--fly()
			--teleport(x, z, 18)
			player:moveTo(CWaypoint(x,z,18),true)

			repeat
				player.X = x
				player.Z = z
				local trash = player:findNearestNameOrId(bizarremechanism, nil, evalfunc)
				if trash then
					player:target(trash)
					myfight(trash)
					player:clearTarget();
				end
			until trash == nil or player.HP < 1 or player.Alive == false

			repeat
				local spawn = player:findNearestNameOrId(guardianrockspirit, nil, evalfunc)
				if spawn then
					player:target(spawn)
					myfight(spawn)
					player:clearTarget();
				end
			until spawn == nil or player.HP < 1 or player.Alive == false
			if (player.HP < 1 or player.Alive == false) then
				logentry = "Player died."
				return
			end
		end
	end
	breaktiles()
end

--=== Start of tiles function ===--
function breaktiles()
	cprintf(cli.yellow,"Digging up tiles...\n")
	if aoefound then
		aoeskill.AutoUse = false
	end
	--=== First run of tiles ===--
	orderlist = {6,24,5,23,4,22,3,21,2,20,1,19,16,36,18,35,17,34,15,33,14,32,13,31,28,12,30,11,29,10,27,9,26,8,25,7}
	for i = 1,#orderlist do
		local tile = tiles[orderlist[i]]
		player:checkPotions()

		-- Dig tile
		local threshold = 120
		if clicktile(tile.Address) == true then
			repeat
				if not RoMScript("TimeKeeperFrame:IsVisible()") then
					return
				end

				--fly()
				if player.X > tile.X+10 or tile.X-10 > player.X or player.Z > tile.Z+10 or tile.Z-10 > player.Z then
					--teleport_SetStepSize(threshold)
					--teleport(tile.X+1,tile.Z+1,18)
					--threshold = threshold/2
					player:moveTo(CWaypoint(tile.X+1,tile.Z+1,18),true)
				end
				player:update()

				if player.Battling then
					repeat
						local _enemy = findaggroenemy()
						if _enemy then
							player:target(_enemy);
							player:fight()
						end
					until _enemy == nil or player.HP < 1 or player.Alive == false
				end

				if (player.HP < 1 or player.Alive == false) then
					logentry = "Player died."
					return
				end

				player:target(tile); yrest(200)
				Attack() yrest(500)
				repeat
					yrest(50)
					player:update()
				until player.Casting == false
			until clicktile(tile.Address) == false
			LastTileDug = i

			-- see what's there
			local starttime = os.clock()
			repeat
				local result = player:findNearestNameOrId({"^"..guardian.."$", guardiandefender,guardianswordsman,franticcliffdemon,brownwoodenchest,smallbomb,mediumbomb,largebomb})
				if result and 15 > distance(player.X,player.Z,result.X,result.Z) then
					result = CPawn(result.Address)
					printf("%s found. Id %d.\n", result.Name, result.Id)
					if result.Name == guardian or result.Name == guardiandefender or result.Name == guardianswordsman or result.Name == franticcliffdemon then
						player:target(result)
						flyoff()
						player:fight()
						if (player.HP < 1 or player.Alive == false) then
							logentry = "Player died."
							return
						end
						if result.Name == franticcliffdemon then
							yrest(500)
							result:update()
							if result.Lootable then
								player:target(result)
								yrest(100)
								Attack()
								yrest(2000)
							end
							-- one more time
							result:update()
							if result.Lootable then
								player:target(result)
								yrest(100)
								Attack()
								yrest(2000)
							end
						end
					elseif result.Id == brownwoodenchest then
						repeat
							player:target(result); yrest(200)
							Attack() yrest(1000)
							repeat
								yrest(50)
								player:update()
							until player.Casting == false
						until clickchest(result.Address) ~= true
					end
					break
				end
			until os.clock() - starttime > 2
		end
	end
	--teleport(2563, 2923) -- tele away from possible bomb.
	--teleport_SetStepSize(120)
	player:moveTo(CWaypoint(2563,2923,18),true)
	chests() -- double check for left over chests. There shouldn't be any
end

--=== Open chests ===--
function chests()
	local chest
	repeat
		chest = player:findNearestNameOrId(brownwoodenchest,nil,clickchest)
		if chest then
			if not RoMScript("TimeKeeperFrame:IsVisible()") then
				return
			end
			--fly()
			--teleport(chest.X+10,chest.Z+10,18)
			player:moveTo(CWaypoint(chest.X+10,chest.Z+10,18),true)
			repeat
				player:target_Object(chest.Id, nil, nil, true);
			until clickchest(chest.Address) ~= true
		end
	until chest == nil
	local secondsleft
	repeat secondsleft = RoMScript("TimeKeeperFrame.startTime-GetTime()") yrest(100) until secondsleft
	local mm = string.format("%2s", math.floor(secondsleft/ 60))
	local ss = string.format("%02s", math.floor(math.fmod(secondsleft, 60)))
	printf("Succeeded at Survival with %s:%s remaining.\n",mm,ss)
	logentry = string.format("Succeeded with %s:%s remaining.",mm,ss)
end

--=== find enemys that have you targeted ===--
function findaggroenemy()
	local obj = nil
	local pawn = nil
	local objectList = CObjectList();
	objectList:update();

	for i = 0,objectList:size() do
		obj = objectList:getObject(i);
		if( obj ~= nil and obj.Type == PT_MONSTER) then
			pawn = CPawn(obj.Address)
			if pawn:isAlive() and pawn.HP > 4 and pawn.TargetPtr == player.Address then
				return pawn
			end
		end
	end
end

--=== Check skills and COMBAT_DISTANCE values
function checkskills()
	-- Needs to be minimum 50
	if settings.profile.options.COMBAT_DISTANCE < 50 then
		cprintf(cli.lightgray,"COMBAT_DISTANCE increased to 50.\n")
		settings.profile.options.COMBAT_DISTANCE = 50
	end

	-- If melee, set COMBAT_DISTANCE to 50
	if settings.profile.options.COMBAT_DISTANCE > 50 and
	   settings.profile.options.COMBAT_TYPE == "melee" and
	   player.Class2 ~= CLASS_SCOUT then
		settings.profile.options.COMBAT_DISTANCE = 50
	end

	-- Recheck skills for COMBAT_DISTANCE of usuable skills
	if settings.profile.options.COMBAT_DISTANCE > 50 then
		local highestrange = 50
		for k,s in pairs(settings.profile.skills) do
			if s.Available and (s.Type == STYPE_DAMAGE or s.Type == STYPE_DOT) and
				AllowSkillCastTime >= s.CastTime and s.Range > highestrange then
					highestrange = s.Range
			end
		end

		if settings.profile.options.COMBAT_DISTANCE > highestrange then
			cprintf(cli.lightgray,"COMBAT_DISTANCE reduced to %d\n",highestrange)
			settings.profile.options.COMBAT_DISTANCE = highestrange
		end
	end

	if settings.profile.options.COMBAT_DISTANCE > 60 then
		attackdistance = 50
		orderlist = {12, 30, 35, 32, 25, 7, 2, 5}
	else
		attackdistance = 25
		orderlist = {6, 12, 18, 24, 30, 36, 35, 34, 33, 32, 31, 25, 19, 13, 7, 1, 2, 3, 4, 5}
	end

	-- assume if user wants to use aoe it will be first attack skill
	-- find aoe skill
	aoefound = false
	firstskillpriority = nil
	player:update()
	for k,v in pairs(settings.profile.skills) do
		if v.Available and (v.Type == STYPE_DAMAGE or v.Type == STYPE_DOT) then
			if not firstskillpriority then firstskillpriority = v.priority end
			aoeskill = settings.profile.skills[k]
			for k,v in pairs(aoeskills) do
				if aoeskill.Name == v then
					cprintf(cli.yellow,"AOE skill found as first skill, %s\n",aoeskill.Name)
					aoefound = true
					aoeskill.AutoUse = true
					aoeskill.MobCount = 1
					aoeskill.maxuse = 0
					aoeskill.priority = firstskillpriority + 1
					if aoeskill.Range > settings.profile.options.COMBAT_DISTANCE then
						cprintf(cli.lightgray,"COMBAT_DISTANCE increased to %d.\n",aoeskill.Range)
						settings.profile.options.COMBAT_DISTANCE = aoeskill.Range
					end
					table.sort(settings.profile.skills, function(a,b) return a.priority > b.priority end)
					attackdistance = 35
					attackrange = 52
					orderlist = {12, 30, 35, 32, 25, 7, 2, 5}
					break
				end
			end

			if not aoefound then
				for k,v in pairs(aoeskillsbig) do
					if aoeskill.Name == v then
						cprintf(cli.yellow,"AOE skill found as first skill, %s\n",aoeskill.Name)
						aoefound = true
						aoeskill.AutoUse = true
						aoeskill.MobCount = 1
						aoeskill.priority = firstskillpriority + 1
						attackrange = aoeskill.Range
						if aoeskill.Range > settings.profile.options.COMBAT_DISTANCE then
							cprintf(cli.lightgray,"COMBAT_DISTANCE increased to %d.\n",aoeskill.Range)
							settings.profile.options.COMBAT_DISTANCE = aoeskill.Range
						end
						table.sort(settings.profile.skills, function(a,b) return a.priority > b.priority end)
						attackdistance = 35
						attackrange = 75
						orderlist = {18, 34, 19, 3,}
						break
					end
				end
			end
		end

		if aoefound == true then break end
	end
	settings.profile.options.COMBAT_STOP_DISTANCE = settings.profile.options.COMBAT_DISTANCE
end

local wp = __WPL.Waypoints[__WPL.CurrentWaypoint]
if distance(player.X,player.Z,wp.X,wp.Z) > 350 then
	print("Too far to run script.")
	logentry = "Too far to run script."
	checkRelog()
	return
end

--=== Table numbering system ===--
--[[
	1 7  13 19 25 31
	2 8  14 20 26 32
	3 9  15 21 27 33
	4 10 16 22 28 34
	5 11 17 23 29 35
	6 12 18 24 30 36
* -- entrance to room.
]]

--=== Turn off looting, wastes time ===--
settings.profile.options.LOOT = true -- false
settings.profile.options.TARGET_LEVELDIF_ABOVE = "15" -- Need to try to kill anything in there or no point.
settings.profile.options.TARGET_LEVELDIF_BELOW = "100" -- trash is lvl 15 which people won't allow for in profile.
settings.profile.options.ANTI_KS = false
settings.profile.options.MAX_TARGET_DIST = 300        -- Or it wont attack trash mobs too far from the door.
--teleport_SetStepSize(120)
