User File #21819933019508761

Upload All User Files

#21819933019508761 - Advance Wars: Dual Strike TAS Bot

tasbot.lua
756 downloads
Uploaded 4/1/2015 3:34 PM by ALAKTORN (see all 34)
require 'resources'

-- Functions to get info for input creation
local function getActivePowerInfo(COAddr)
	local value = memory.readbyte(COAddr + 1)
	local TAGorSCOPActive = bit.band(value, 8)
	local COPActive = bit.band(value, 4)
	return TAGorSCOPActive + COPActive
end

local function getNumPowersUsed(COAddr)
	local value = memory.readbyte(COAddr + 1) + bit.lshift(memory.readbyte(COAddr + 2), 8)
	return bit.rshift(value, 6) % 16
end

local function getPowerCostFactor(Usages)
	if Usages >= 10 then
		return 2
	else
		return 1 + Usages * 0.2
	end
end

local function getNumUseablePowers()
	local COAddr = 0x218936C
	local COID = memory.readbyte(COAddr)
	if COID > 0 then
		local PowersUsed = getNumPowersUsed(COAddr)
		local Factor = getPowerCostFactor(PowersUsed)
		local COPCost = getCOPCost(COID)
		local SCOPCost = getSCOPCost(COID)
		local Stars = bit.rshift(memory.readword(COAddr + 2), 4) / 100
		
		local ActivePower = getActivePowerInfo(COAddr)
		if ActivePower ~= 0 then
			return 0
		end
		
		local PowerCount = 0
		-- COP charged?
		if Stars >= COPCost * Factor then
			-- Von Bolt
			if COPCost ~= -1 then
				PowerCount = PowerCount + 1
			end
			-- SCOP charged?
			if Stars >= SCOPCost * Factor then
				PowerCount = PowerCount + 1
				-- TAG?
				local COAddr2 = 0x2189370
				local COID2 = memory.readbyte(COAddr2)
				if COID2 > 0 then
					local PowersUsed2 = getNumPowersUsed(COAddr2)
					local Factor2 = getPowerCostFactor(PowersUsed2)
					local SCOPCost2 = getSCOPCost(COID2)
					local Stars2 = bit.rshift(memory.readword(COAddr2 + 2), 4) / 100
					
					if Stars2 >= SCOPCost2 * Factor2 then
						PowerCount = PowerCount + 1
					end
				end
			end
		end
		return PowerCount
	end
	return nil
end

local function getNumCOs()
	local COAddr2 = 0x2189370
	local COID2 = memory.readbyte(COAddr2)
	if COID2 ~= 0 then
		return 2
	else
		return 1
	end
end

-- Coordinates and position related stuff
mapCoords = {0,0}
function readMapCoords()
	local x = memory.readdword(0x27C3AE0) - 19
	local y = memory.readdword(0x27C3AE4) - 14
	if x >= 0 and x < 60 and y >= 0 and y < 60 then
		if x ~= mapCoords[1] or y ~= mapCoords[2] then
			mapCoords = {x,y}
		end
	end
end

function getMapCoords()
	return mapCoords
end

-- Get it in screen space
local function getCursorPos()
	local mapX = memory.readbyte(0x2183B30)
	local mapY = memory.readbyte(0x2183B32)
	local mapCoords = getMapCoords()
	local screenX = mapX - mapCoords[1]
	local screenY = mapY - mapCoords[1]
	return {screenX, screenY}
end

local function mapToScreenPos(mapPos)
	local mapCoords = getMapCoords()
	local screenX = mapPos[1] - mapCoords[1]
	local screenY = mapPos[2] - mapCoords[2]
	return {screenX, screenY}
end

local function isPos(Pos1, Pos2)
	return (Pos1[1] == Pos2[1]) and (Pos1[2] == Pos2[2])
end

local function isAdjPos(Pos1, Pos2)
	return math.abs(Pos1[1] - Pos2[1]) <= 1 and math.abs(Pos1[2] - Pos2[2]) <= 1
end

local function addPos(Pos1, Pos2)
	return {Pos1[1] + Pos2[1],Pos1[2] + Pos2[2]}
end

local function subPos(Pos1, Pos2)
	return {Pos1[1] - Pos2[1],Pos1[2] - Pos2[2]}
end

-- Screen to pixel space
local function screenToPixelX(x, y)
	-- on y = 0  width ~= 13.6
	-- on y = 11 width ~= 15.8
	local squareWidth = 13.6 + 0.2 * y
	return 127 + squareWidth * (x - 7.5)
end

local function screenToPixelY(y)
	local Y = {21, 33, 46, 59, 73, 87, 101, 115, 130, 146, 162, 179}
	return Y[(y + 1)]
end

local function screenPosToPixelPos(screenPos)
	local x = screenToPixelX(screenPos[1], screenPos[2])
	local y = screenToPixelY(screenPos[2])
	return {x,y}
end

-- Helper functions to create input
function inputBeginMap(Time)
	local time1 = Time .. '-15'
	local time2 = Time + 3
	Input[time1] = { ['buttons'] = { 'start'}}
	Input[time2] = { ['stylus'] = {0,0}}
	
	return 9
end

function inputBeginDay(Time, PerfectTouch)
	Input[Time] = { ['stylus'] = {0,0}}
	
	if PerfectTouch then
		return 33
	else
		return 34
	end
end

function inputSupply(Time, SupplyTime)
	return SupplyTime
end

function inputScroll(Time, ScrollPos)
	local time1 = Time
	local time2 = (time1 + 1) .. '-14'
	local time3 = (time1 + 1) .. '-20'
	Input[time1] = { ['buttons'] = {'start'}}
	Input[time2] = { ['stylus'] = ScrollPos}
	Input[time3] = { ['buttons'] = {'start'}}
	
	return 2
end

function inputBuild(Time, BasePos, Unit)
	BasePos = mapToScreenPos(BasePos)
	
	local UnitPos
		if Unit == 'Infantry' then		UnitPos = {64,64}
	elseif Unit == 'Mech' then			UnitPos = {64,80}
	elseif Unit == 'Recon' then			UnitPos = {64,96}
	elseif Unit == 'Tank' then			UnitPos = {64,112}
	elseif Unit == 'Md Tank' then		UnitPos = {64,128}
	elseif Unit == 'Neotank' then		UnitPos = {64,144}
	elseif Unit == 'Megatank' then		UnitPos = {64,160}
	elseif Unit == 'APC' then			UnitPos = {192,64}
	elseif Unit == 'Artillery' then		UnitPos = {192,80}
	elseif Unit == 'Rockets' then		UnitPos = {192,96}
	elseif Unit == 'Anti-Air' then		UnitPos = {192,112}
	elseif Unit == 'Missiles' then		UnitPos = {192,128}
	elseif Unit == 'Piperunner' then	UnitPos = {192,144}
	end
	
	local time1 = Time
	local time2 = time1 + 1
	local time3 = time2 + 1
	Input[time1] = { ['cursor'] = BasePos, ['buttons'] = {'A'}}
	Input[time2] = { ['stylus'] = UnitPos}
	Input[time3] = { ['buttons'] = {'A'}}
	
	return 5
end

function inputMov(Time, UnitPos, Movement, Options)
	UnitPos = mapToScreenPos(UnitPos)
	
	local MovPos = addPos(UnitPos, Movement)
	local numSquares
	if Options and Options['squares'] then
		numSquares = Options['squares']
	else
		numSquares = math.abs(Movement[1]) + math.abs(Movement[2])
	end
	
	local time1
	-- If the cursor is adjacent we need tap away, 1 frame lost
	if Options and Options['cursorAdj'] then
		local CursorPos = getCursorPos()
		local AwayPos
		if UnitPos[1] >= 8 and UnitPos[2] >= 6 then
			AwayPos = {2,2}
		elseif UnitPos[1] <= 7 and UnitPos[2] >= 6 then
			AwayPos = {13,2}
		elseif UnitPos[1] >= 8 and UnitPos[2] <= 5 then
			AwayPos = {2,9}
		else--if UnitPos[1] <= 7 and UnitPos[2] <= 5 then
			AwayPos = {13,9}
		end
		local timeA = Time
		Input[timeA] = { ['stylus'] = AwayPos}
		time1 = timeA + 1
	else
		time1 = Time
	end
	
	-- Select with removing lag
	if Options and Options['removeLag'] then
		TempPos = mapToScreenPos(Options['removeLag'])
		local timeA = Time
		local timeB = timeA + 1
		time1 = timeB + 1
		Input[timeA] = { ['cursor'] = TempPos}
		Input[timeB] = { ['cursor'] = UnitPos}
		Input[time1] = { ['buttons'] = {'A'}}
	-- Select with Tap+A
	elseif (Options and (Options['RNGPath'] or Options['RNGPaths'])) or (Options and Options['cursorAdj']) or isAdjPos(UnitPos, MovPos) then
		Input[time1] = { ['cursor'] = UnitPos, ['buttons'] = {'A'}}
	-- Select with just Tap
	else
		Input[time1] = { ['cursor'] = UnitPos}
	end
	
	-- Draw a path to advance the RNG if specified
	if Options and Options['RNGPath'] then
		PathPos = addPos(UnitPos, Options['RNGPath'])
		local time1A = time1 + 1
		Input[time1A] = { ['cursor'] = PathPos}
	elseif Options and Options['RNGPaths'] then
		local time1X = time1 + 1
		for i, Path in pairs(Options['RNGPaths']) do
			PathPos = addPos(UnitPos, Path)
			Input[time1X] = { ['cursor'] = PathPos}
			time1X = time1X + 1
		end
		Options['RNGPathDelay'] = table.getn(Options['RNGPaths']) - 1
		time1 = time1 + Options['RNGPathDelay']
	-- If the cursor was adjacent before or we make an adjacent movement then we need to move the cursor away
	elseif (Options and Options['cursorAdj']) or isAdjPos(UnitPos, MovPos) then
		local time1A = time1 + 1
		-- Loading needs the d-pad
		if Options and Options['load'] then
			local dirButtons
			if isPos(Movement, {0,-1}) then
				dirButtons = {'down'}
			elseif isPos(Movement, {1,-1}) then
				dirButtons = {'left','down'}
			elseif isPos(Movement, {1,0}) then
				dirButtons = {'left'}
			elseif isPos(Movement, {1,1}) then
				dirButtons = {'left','up'}
			elseif isPos(Movement, {0,1}) then
				dirButtons = {'up'}
			elseif isPos(Movement, {-1,1}) then
				dirButtons = {'right', 'up'}
			elseif isPos(Movement, {-1,0}) then
				dirButtons = {'right'}
			elseif isPos(Movement, {-1,-1}) then
				dirButtons = {'right','down'}
			else
				dirButtons = {'b'}
			end
			Input[time1A] = { ['buttons'] = dirButtons}
		-- Otherwise just tap away
		else
			local AwayPos
			if UnitPos[1] >= 8 and UnitPos[2] >= 6 then
				AwayPos = {2,2}
			elseif UnitPos[1] <= 7 and UnitPos[2] >= 6 then
				AwayPos = {13,2}
			elseif UnitPos[1] >= 8 and UnitPos[2] <= 5 then
				AwayPos = {2,9}
			else--if UnitPos[1] <= 7 and UnitPos[2] <= 5 then
				AwayPos = {13,9}
			end
			Input[time1A] = { ['stylus'] = AwayPos}
		end
	end
	
	-- Choose the unit menu option, most of the time we just go by default
	local MenuPos
	if Options and Options['menu'] then
		MenuPos = { MovPos[1], MovPos[2] + Options['menu'] - 1}
	else
		MenuPos = MovPos
	end
	
	local time2 = time1 + 2
	local time3 = time2 + 3 + 4 * numSquares
	Input[time2] = { ['cursor'] = MovPos, ['buttons'] = {'A'}}
	Input[time3] = { ['cursor'] = MenuPos}
	
	-- Drop an Inf or Mech
	if Options and Options['drop'] then
		local DropPos = { MovPos[1] + Options['drop'][1], MovPos[2] + Options['drop'][2]}
		local time4 = time3 + 2
		Input[time4] = { ['cursor'] = DropPos, ['buttons'] = {'A'}}
	end
	
	-- Attack a unit
	if Options and Options['attack'] then
		local time4 = time3 + 12
		if Options and Options['RNGWait'] then
			time4 = time4 + Options['RNGWait']
		end
		-- We might need to select the unit if autotarget doesn't work, 1 frame lost
		if Options and Options['targetPos'] then
			local TargetPos = mapToScreenPos(Options['targetPos'])
			Input[time4] = { ['cursor'] = TargetPos}
		else
			Input[time4] = { ['buttons'] = {'A'}}
		end
	end
	
	local ActionLength = 8 + 4 * numSquares
	if Options and Options['removeLag'] then
		ActionLength = ActionLength + 2
	end
	if Options and Options['cursorAdj'] then
		ActionLength = ActionLength + 1
	end
	if Options and Options['RNGPathDelay'] then
		ActionLength = ActionLength + Options['RNGPathDelay']
	end
	if Options and Options['load'] then
		ActionLength = ActionLength - 1
	end
	if Options and Options['drop'] then
		ActionLength = ActionLength + 6
	end
	if Options and Options['cap'] then
		ActionLength = ActionLength + 112 + Options['cap']
	end
	if Options and Options['finish'] then
		ActionLength = ActionLength + 20
	end
	if Options and Options['attack'] then
		ActionLength = ActionLength + 11
		if Options and Options['RNGWait'] then
			ActionLength = ActionLength + Options['RNGWait']
		end
		if Options and Options['targetPos'] then
			ActionLength = ActionLength + 1
		end
		if Options and Options['attDelay'] then
			ActionLength = ActionLength + Options['attDelay']
		else
			ActionLength = ActionLength + 3
		end
	end
	return ActionLength
end

function inputCap(Time, UnitPos, Cap, Options)
	UnitPos = mapToScreenPos(UnitPos)
	
	-- Choose the menu option, most of the time we just go by default
	local MenuPos
	if Options and Options['menu'] then
		MenuPos = { UnitPos[1], UnitPos[2] + Options['menu'] - 1}
	else
		MenuPos = UnitPos
	end
	local time1 = Time
	local time2 = time1 + 3
	Input[time1] = { ['cursor'] = UnitPos, ['buttons'] = {'A'}}
	Input[time2] = { ['cursor'] = MenuPos}
	
	local ActionLength = 6 + 112 + Cap
	if Options and Options['finish'] then
		ActionLength = ActionLength + 20
	end
	return ActionLength
end

function inputAtt(Time, UnitPos, Options)
	UnitPos = mapToScreenPos(UnitPos)
	
	local time1
	-- If the cursor is adjacent we need to use the d-pad, 1 frame lost
	if Options and Options['cursorAdj'] then
		local CursorPos = getCursorPos()
		local CursorDist = subPos(UnitPos, CursorPos)
		local dirButtons
		if isPos(CursorDist, {0,-1}) then
			dirButtons = {'down'}
		elseif isPos(CursorDist, {1,-1}) then
			dirButtons = {'left','down'}
		elseif isPos(CursorDist, {1,0}) then
			dirButtons = {'left'}
		elseif isPos(CursorDist, {1,1}) then
			dirButtons = {'left','up'}
		elseif isPos(CursorDist, {0,1}) then
			dirButtons = {'up'}
		elseif isPos(CursorDist, {-1,1}) then
			dirButtons = {'right', 'up'}
		elseif isPos(CursorDist, {-1,0}) then
			dirButtons = {'right'}
		elseif isPos(CursorDist, {-1,-1}) then
			dirButtons = {'right','down'}
		else
			dirButtons = {'b'}
		end
		local timeA = Time
		Input[timeA] = { ['buttons'] = dirButtons}
		time1 = timeA + 1
	else
		time1 = Time
	end
	
	local time2
	-- Select with removing lag
	if Options and Options['removeLag'] then
		local time1A = time1 + 2
		Input[time1] = { ['cursor'] = UnitPos}
		Input[time1A] = { ['buttons'] = {'A'}}
		time2 = time1A + 2
	-- Select with Tap+A
	else
		Input[time1] = { ['cursor'] = UnitPos, ['buttons'] = {'A'}}
		time2 = time1 + 3
	end
	Input[time2] = { ['cursor'] = UnitPos}
	
	local time3 = time2 + 12
	if Options and Options['RNGWait'] then
		time3 = time3 + Options['RNGWait']
	end
	-- We might need to select the unit if autotarget doesn't work, 1 frame lost
	if Options and Options['targetPos'] then
		local TargetPos = mapToScreenPos(Options['targetPos'])
		Input[time3] = { ['cursor'] = TargetPos}
	else
		Input[time3] = { ['buttons'] = {'A'}}
	end
	
	local ActionLength = 17
	if Options and Options['removeLag'] then
		ActionLength = ActionLength + 1
	end
	if Options and Options['cursorAdj'] then
		ActionLength = ActionLength + 1
	end
	if Options and Options['adj'] then
		ActionLength = ActionLength + Options['adj']
	end
	if Options and Options['RNGWait'] then
		ActionLength = ActionLength + Options['RNGWait']
	end
	if Options and Options['targetPos'] then
		ActionLength = ActionLength + 1
	end
	if Options and Options['attDelay'] then
		ActionLength = ActionLength + Options['attDelay']
	else
		ActionLength = ActionLength + 3
	end
	return ActionLength
end

function inputExpBB(Time, UnitPos)
	UnitPos = mapToScreenPos(UnitPos)
	
	local time1 = (Time + 2) .. '-14'
	local time2 = (Time + 2) .. '-29'
	Input[time1] = { ['buttons'] = {'start'}}
	Input[time2] = { ['buttons'] = {'A'}}
	
	return 108
end

local function inputMenu(Time, MenuItem, Options)
	local MenuPos = {128,(MenuItem + 1) * 16}
	
	-- Move the cursor while opening the menu
	if Options and Options['cursor'] then
		local NewCursorPos = mapToScreenPos(Options['cursor'])
		local time1 = Time
		local time2 = time1 + 1
		local time3 = time2 + 1
		Input[time1] = { ['cursor'] = NewCursorPos, ['buttons'] = {'select'}}
		Input[time2] = { ['stylus'] = MenuPos}
		Input[time3] = { ['buttons'] = {'A'}}
	else
		local time1 = Time
		local time2 = time1 + 1
		Input[time1] = { ['buttons'] = {'select'}}
		Input[time2] = { ['stylus'] = MenuPos}
	end
end

function inputUseCOP(Time, Units, PerfectTouch, Options)
	inputMenu(Time, 3, Options)
	
	local time1 = (Time + 6) .. '-14'
	Input[time1] = { ['buttons'] = {'start'}}
	
	local ActionLength = 53
	if Units > 0 then
		ActionLength = ActionLength + 20 + 5 * Units
	end
	if PerfectTouch and PerfectTouch == true then
		ActionLength = ActionLength - 1
	end
	return ActionLength
end

function inputUseSCOP(Time, Units, PerfectTouch, Options)
	inputMenu(Time, 4, Options)
	
	local time1 = (Time + 6) .. '-14'
	Input[time1] = { ['buttons'] = {'start'}}
	
	local ActionLength = 132
	if Units > 0 then
		ActionLength = ActionLength + 26 + 5 * Units
	end
	if PerfectTouch and PerfectTouch == true then
		ActionLength = ActionLength - 1
	end
	return ActionLength
end

function inputUseTAG(Time, Units, PerfectTouch, Options)
	inputMenu(Time, 5, Options)
	
	local time1 = (Time + 6) .. '-14'
	Input[time1] = { ['buttons'] = {'start'}}
	
	local ActionLength = 190
	if Units > 0 then
		ActionLength = ActionLength + 26 + 5 * Units
	end
	if PerfectTouch and PerfectTouch == true then
		ActionLength = ActionLength - 1
	end
	return ActionLength
end

function inputTAGSwap(Time, Units, PerfectTouch, Options)
	inputMenu(Time, 5, Options)
	
	local time1 = (Time + 6) .. '-14'
	Input[time1] = { ['buttons'] = {'start'}}
	
	local ActionLength = 84
	if Units > 0 then
		ActionLength = ActionLength + 26 + 5 * Units
	end
	if PerfectTouch and PerfectTouch == true then
		ActionLength = ActionLength - 1
	end
	return ActionLength
end

function inputEndDay(Time, Options)
	local PowerCount = getNumUseablePowers()
	local NumCOs = getNumCOs()
	if NumCOs == 1 or (Options and (Options['swap'] or Options['tag'])) then
		inputMenu(Time, 5 + PowerCount, Options)
	else
		inputMenu(Time, 6 + PowerCount, Options)
	end
	
	if Options and Options['swap'] then
		local time1 = (Time + 6) .. '-14'
		Input[time1] = { ['buttons'] = {'start'}}
	end
	
	local ActionLength = 13
	if Options and Options['swap'] then
		ActionLength = ActionLength + 40
	end
	return ActionLength
end

function inputAIBeginDay(AITime, Options)
	if Options and Options['RNGWait'] then
		AITime = AITime + Options['RNGWait']
	end
	AIInput[AITime] = true
end

-- Helper functions
-- RNG
local function Multiply(int1, int2)
	local High1 = math.floor(int1 / 32768)
	local Low1 = math.floor(int1 % 32768)
	local High2 = math.floor(int2 / 32768)
	local Low2 = math.floor(int2 % 32768)
	
	local NewHigh = (High1 * Low2 + High2 * Low1) % 32768;
	local NewLow = Low1 * Low2
	
	return (NewHigh * 32768 + NewLow) % 1073741824;
end

function RNGStep(i)
	local i4 = (i * 4 + 1) % 1073741824
	return Multiply(i4, i + 1)
end

local function RNGSteps(iRNG, iTimes)
	while iTimes > 0 do
		iRNG = RNGStep(iRNG)
		iTimes = iTimes - 1
	end
	return iRNG
end

local function getNumSteps(oldRNG, newRNG)
	if oldRNG == newRNG then
		return 0
	end
	for i = 1, 200 do
		oldRNG = RNGStep(oldRNG)
		if oldRNG == newRNG then
			return i
		end
	end
	return -1;
end

local function RNGFrameAdv(iRNG, GameTime)
	local x = GameTime + RNGStep(iRNG)
	local newRNG = RNGStep(x)
	newRNG = newRNG + (math.floor(newRNG / 2) % 2)
	return newRNG
end

local function RNGMenuAdv(iRNG)
	for i = 1, 80 do
		iRNG = RNGStep(iRNG)
		iRNG = iRNG + (math.floor(iRNG / 2) % 2)
	end
	return iRNG
end

-- The core functions of the TASBot
Strategy = {}
nextAction = -1
function resetStrategy()
	Input = {}
	Strategy = {}
	if SurvivalMapCounter == 0 then		nextAction = 35
	else								nextAction = 16
	end
end

function addInput(Name, Param1, Param2, Param3, Param4)
	table.insert(Strategy, { Name, Param1, Param2, Param3, Param4})
end

function loadStrategyInput()
	while next(Strategy) ~= nil and MapTimer >= nextAction do
		local Action = Strategy[1]
		table.remove(Strategy, 1)
		
		local ActionLength
			if Action[1] == 'BeginMap' then
			ActionLength = inputBeginMap(nextAction)
		elseif Action[1] == 'BeginDay' then
			ActionLength = inputBeginDay(nextAction, Action[2])
		elseif Action[1] == 'Supply' then
			ActionLength = inputSupply(nextAction, Action[2])
		elseif Action[1] == 'Scroll' then
			ActionLength = inputScroll(nextAction, Action[2])
		elseif Action[1] == 'Build' then
			ActionLength = inputBuild(nextAction, Action[2], Action[3])
		elseif Action[1] == 'Mov' then
			ActionLength = inputMov(nextAction, Action[2], Action[3], Action[4])
		elseif Action[1] == 'MovLoad' then
			local Options = {}
			if Action[4] then Options = Action[4] end
			Options['load'] = true
			ActionLength = inputMov(nextAction, Action[2], Action[3], Options)
		elseif Action[1] == 'Cap' then
			ActionLength = inputCap(nextAction, Action[2], Action[3], Action[4])
		elseif Action[1] == 'MovCap' then
			local Options = {}
			if Action[5] then Options = Action[5] end
			Options['cap'] = Action[4]
			ActionLength = inputMov(nextAction, Action[2], Action[3], Options)
		elseif Action[1] == 'Att' then
			ActionLength = inputAtt(nextAction, Action[2], Action[3])
		elseif Action[1] == 'MovAtt' then
			local Options = {}
			if Action[4] then Options = Action[4] end
			Options['attack'] = true
			ActionLength = inputMov(nextAction, Action[2], Action[3], Options)
		elseif Action[1] == 'MovExpBB' then
			ActionLength = inputMov(nextAction, Action[2], Action[3], Action[4])
			nextAction = nextAction + ActionLength
			ActionLength = inputExpBB(nextAction, addPos(Action[2], Action[3]))
		elseif Action[1] == 'UseCOP' then
			ActionLength = inputUseCOP(nextAction, Action[2], Action[3], Action[4])
		elseif Action[1] == 'UseSCOP' then
			ActionLength = inputUseSCOP(nextAction, Action[2], Action[3], Action[4])
		elseif Action[1] == 'UseTAG' then
			ActionLength = inputUseTAG(nextAction, Action[2], Action[3], Action[4])
		elseif Action[1] == 'TAGSwap' then
			ActionLength = inputTAGSwap(nextAction, Action[2], Action[3], Action[4])
		elseif Action[1] == 'EndDay' then
			ActionLength = inputEndDay(nextAction, Action[2])
			inputAIBeginDay(AIMapTimer + 6, Action[2])
		elseif Action[1] == 'Swap' then
			local Options = {}
			if Action[2] then Options = Action[2] end
			Options['swap'] = true
			ActionLength = inputEndDay(nextAction, Options)
			inputAIBeginDay(AIMapTimer + 6, Options)
		end
		nextAction = nextAction + ActionLength
	end
end

local lastMap = -1
local lastMapTimer = -1
function loadPlayerInput()
	local Map = (SurvivalMapCounter % 11) + 1
	-- Reload the strategy if we aren't on the same map with the same or +1 timer or just finished the map and haven't started the next yet
	if not ((Map == lastMap or Map == lastMap + 1) and (MapTimer == lastMapTimer or MapTimer == lastMapTimer + 1)) then
		resetStrategy()
		loadStrategy(Map)
		emu.message('Strategy for Map ' .. Map .. ' loaded')
	end
	lastMap = Map
	lastMapTimer = MapTimer
	
	-- If the Map timer frame is not 1 then we already loaded the input
	if MapTimerFrame ~= 1 then
		return
	end
	
	loadStrategyInput()
end

-- Set input that will be executed in the current frame
function setInput(input)
	if input['buttons'] then
		for k, button in pairs(input['buttons']) do
			joypad.set(1, { [button] = 1})
		end
	end
	if input['stylus'] then
		local tp = { ['x'] = input['stylus'][1], ['y'] = input['stylus'][2], ['touch'] = 1}
		stylus.set(tp)
	end
	if input['cursor'] then
		local scrPos = screenPosToPixelPos(input['cursor'])
		local tp = { ['x'] = scrPos[1], ['y'] = scrPos[2], ['touch'] = 1}
		stylus.set(tp)
	end
end

Input = {}
local function getPlayerInput(Time, TimeFrame)
	if Input[Time .. '-' .. TimeFrame] then
		return Input[Time .. '-' .. TimeFrame]
	elseif Input[Time] then
		return Input[Time]
	else
		return nil
	end
end

function executePlayerInput()
	local input = getPlayerInput(MapTimer, MapTimerFrame)
	if input then
		setInput(input)
	end
end

AIDayActive = false
function executeAIInput()
	if AIDayActive then
		setInput({ ['buttons'] = {'A'}})
	end
end

AIInput = {}
function executeAIBeginDay()
	-- Begin AI day ASAP
	if AIInput[AIMapTimer] then
		setInput({ ['stylus'] = {0,0}})
	end
end

MenuInput = {}
function addMenuInput(GameTime, GameTimeFrame, Input)
	MenuInput[GameTime .. '-' .. GameTimeFrame] = Input
end

local function getMenuInput(GameTime, GameTimeFrame)
	if MenuInput[GameTime .. '-' .. GameTimeFrame] then
		return MenuInput[GameTime .. '-' .. GameTimeFrame]
	else
		return nil
	end
end

function executeMenuInput()
	local input = getMenuInput(GameTimer, GameTimerFrame)
	if input then
		setInput(input)
	end
end

local MapTimerAddr = 0x2189394
MapTimer = -1
MapTimerFrame = -1
function readMapTimer()
	local newMapTime = memory.readdword(MapTimerAddr)
	if MapTimer == newMapTime then
		MapTimerFrame = MapTimerFrame + 1
	else
		MapTimer = newMapTime
		MapTimerFrame = 1
		-- Player map timer increased? Then AI Day is certainly over
		AIDayActive = false
	end
end

local AIMapTimerAddr = 0x218942C
AIMapTimer = -1
function readAIMapTimer()
	AIMapTimer = memory.readdword(AIMapTimerAddr)
end

local GameTimerAddr = 0x27C0738
GameTimer = -1
GameTimerFrame = -1
function readGameTimer()
	local newGameTime = memory.readdword(GameTimerAddr)
	if GameTimer == newGameTime then
		GameTimerFrame = GameTimerFrame + 1
	else
		GameTimer = newGameTime
		GameTimerFrame = 1
	end
end

local RNGAddr = 0x216E2B4
RNG = -1
TotalNumSteps = 0
function readRNG(printChanges)
	newRNG = memory.readdword(RNGAddr)
	if RNG ~= newRNG then
		local printText = ''
		if RNG ~= -1 then
			local numSteps = getNumSteps(RNG, newRNG)
			if numSteps ~= -1 then
				TotalNumSteps = TotalNumSteps + numSteps
				printText = newRNG .. ' (' .. numSteps .. ')' .. ' Total: ' .. TotalNumSteps .. ' at ' .. GameTimer
			else
				TotalNumSteps = 0
				-- FrameBasedAdvance! AI Day started
				if newRNG == RNGFrameAdv(RNG, GameTimer) then
					printText = newRNG .. ' (Frame ' .. GameTimer .. ')'
					AIDayActive = true
				else
					local menuRNG = RNG
					for i = 1, 10 do
						menuRNG = RNGMenuAdv(menuRNG)
						if newRNG == menuRNG then
							printText = newRNG .. ' (Menu * ' .. i .. ' at ' .. GameTimer .. ')'
							AIDayActive = false
						end
					end
				end
			end
		end
		if printChanges then
			if printText ~= '' then
				print(printText)
			else
				print(newRNG)
			end
		end
		RNG = newRNG
	end
end
function cheatRNG(Value)
	memory.writedword(RNGAddr, Value)
end

local SurvivalMapCounterAddr = 0x22A7B86
SurvivalMapCounter = -1
function readSurvivalMapCounter()
	SurvivalMapCounter = memory.readbyte(SurvivalMapCounterAddr)
end
function cheatSurvivalMapCounter(Value)
	memory.writebyte(SurvivalMapCounterAddr, Value)
end