User File #68382326867286620

Upload All User Files

#68382326867286620 - Shaman King: Master of Spirits lua

sk1.lua
272 downloads
Uploaded 12/27/2020 2:24 PM by xy2_ (see all 139)
-- GBA Shaman King - Master of Spirits, TASing script
-- Ram watch, Yoh and enemy information

local max_entities = 23
local color = {
	opaque = {
		[0] = 0xFF00FF00, -- Green
		0xFFFFFF00, -- Yellow
		0xFFFF0000, -- Red
		0xFFBA8E7D, -- Brown
		0xFF0000FF, -- Blue
		0xFF665046 -- Dark brown
	},
	trans = {
		[0] = 0x7700FF00, -- Green
		0x77FFFF00, -- Yellow
		0x77FF0000, -- Red
		0x77BA8E7D, -- Brown
		0x770000FF, -- Blue
		0x77665046, -- Dark brown
		["lightred"] = 0x77FF0000
	}
}

client.SetGameExtraPadding(0, 0, 40, 0)

local Queue = {head = 0, tail = 0, full = false}

-- Circular buffer implementation in Lua
function Queue:new(o)
	o = o or {}
	setmetatable(o, self)
	self.__index = self
	return o
end

function Queue:empty()
	return (not self.full) and self.head == self.tail
end

function Queue:_advance()
	-- If the buffer is full, the start point "rotates",
	-- so we advance both head and tail
	if self.full then
		self.tail = (self.tail + 1) % self.size
	end

	self.head = (self.head + 1) % self.size
	-- Has the queue become full?
	self.full = self.head == self.tail
end

function Queue:enqueue(value)
	-- Will overwrite values when the buffer is full.
	self[self.head] = value
	self:_advance()
end

-- Returns the first element in the queue (the oldest).
function Queue:get()
	if self:empty() then
		error("get: queue is empty")
	end
	return self[self.tail]
end

-- Returns the last element in the queue (the newest).
-- If the queue is full, head == tail,
-- so we must go backwards to get the last element of the queue.
function Queue:get_head()
	if self:empty() then
		error("get_head: queue is empty")
	end
	head = (self.head - 1) % self.size
	return self[head]
end

-- Returns first element and advances the tail pointer.
-- Useful for getting all values of the list in constant space.
-- At the end, tail returns to its original value.
function Queue:get_advance(list)
	value = self[self.tail]
	self.tail = (self.tail + 1) % self.size
	return value
end

-- General RAM watch, and value display on screen
local function DisplayHud(x, y)
	-- SP
	local sp = memory.read_u16_le(0x22BE, "IWRAM")
	gui.pixelText(x + 0, y + 0, string.format("%4d", sp), 0xFFFFFFFF, color.trans[4])

	-- SP refill
	local sprefill = memory.read_u16_le(0x22CA, "IWRAM")
	if sprefill == 0 then
		gui.pixelText(x + 0, y + 7, string.format("%4d", sprefill), 0xFFFFFFFF, color.trans[0])
	else
		gui.pixelText(x + 0, y + 7, string.format("%4d", sprefill), 0xFFFFFFFF, color.trans[2])
	end

	-- In-game time (current segment)
	local igtframeseg = memory.read_u32_le(0x36DD, "IWRAM")
	gui.pixelText(x + 180, y + 0, string.format("%8d", igtframeseg), 0xFFFFFFFF, color.trans[0])
	-- In-game time (current area)
	local igtframearea = memory.read_u32_le(0x3629, "IWRAM")
	gui.pixelText(x + 180, y + 7, string.format("%8d", igtframearea), 0xFFFFFFFF, color.trans[3])
	-- In-game time (global)
	local igtframe = memory.read_u32_le(0x1DD8, "IWRAM")
	gui.pixelText(x + 180, y + 14, string.format("%8d", igtframe), 0xFFFFFFFF, color.trans[5])
	-- Global timer
	local globalframe = memory.read_u32_le(0x1DD4, "IWRAM")
	gui.pixelText(x + 180, y + 21, string.format("%8d", globalframe))

	-- Speed
	local xspeed = memory.read_s32_le(0x3650, "IWRAM")
	local yspeed = memory.read_s32_le(0x3654, "IWRAM")
	gui.pixelText(x + 215, y, string.format("%6d", xspeed))
	gui.pixelText(x + 215, y + 7, string.format("%6d", yspeed))

	-- Ground/air? Displays G on ground, A on air
	local groair = memory.read_u8(0x3614, "IWRAM")
	if groair == 1 then
		gui.pixelText(x + 215, y + 14, "G", 0xFFFFFFFF, color.trans[4])
	else
		gui.pixelText(x + 215, y + 14, "A", 0xFFFFFFFF, color.trans[1])
	end

	-- Buffered down input timer to backdash
	local downbuffer = memory.read_u8(0x25C3, "IWRAM")
	if downbuffer >= 1 then
		gui.pixelText(x + 219, y + 14, "V" .. string.format("%4d", downbuffer), 0xFFFFFFFF, color.trans[0])
	else
		gui.pixelText(x + 219, y + 14, string.format("%5d", downbuffer), 0xFFFFFFFF, color.trans[5])
	end

	-- X and Y position
	local xpos = memory.read_s32_le(0x3648, "IWRAM")
	local ypos = memory.read_s32_le(0x364C, "IWRAM")

	local xmain, ymain = bit.rshift(xpos, 8), bit.rshift(ypos, 8)
	local xsub, ysub = bit.band(bit.lshift(1, 8) - 1, xpos), bit.band(bit.lshift(1, 8) - 1, ypos)

	gui.pixelText(x + 199, y + 35, string.format("X%9d", xpos), 0xFFFFFFFF, color.trans[5])
	gui.pixelText(x + 199, y + 42, string.format("Y%9d", ypos), 0xFFFFFFFF, color.trans[5])
	gui.pixelText(x + 199, y + 49, string.format("X%5d:%3d", xmain, xsub), 0xFFFFFFFF, color.trans.lightred)
	gui.pixelText(x + 199, y + 56, string.format("Y%5d:%3d", ymain, ysub), 0xFFFFFFFF, color.trans.lightred)
end

-- A visual separator for the sidebar
local function Separator(x, y, length)
	gui.drawLine(x + 14, y, x + length, y)

	for i = 0, 6 do
		gui.drawPixel(x + i * 2, y)
	end
end

-- General movie information
local function MovieInfo(x, y)
	local frame = emu.framecount()
	if emu.islagged() == true then
		gui.pixelText(x, y, frame, 0xFFFFFFFF, color.opaque[2])
	else
		gui.pixelText(x, y, frame)
	end

	local lagcount = emu.lagcount()
	gui.pixelText(x, y + 7, lagcount, color.opaque[2])

	-- Shoutouts to Masterjun
	local buttons = {
		["Up"] = "^",
		["Down"] = "v",
		["Left"] = "<",
		["Right"] = ">",
		["Select"] = "s",
		["Start"] = "S",
		["A"] = "A",
		["B"] = "B",
		["L"] = "L",
		["R"] = "R"
	}
	local s = ""
	for k, v in pairs(movie.getinput(frame - 1)) do
		if v == true then
			s = s .. buttons[k]
		end
	end

	gui.pixelText(x, y + 14, s)
end

-- Displays information about Yoh's animations on the sidebar
local function GetYohState(x, y)
	local stateText = {
		[0] = {"Standing", "", ""},
		{"Walking", "", ""},
		{"Neutral", "", ""},
		{"Crouching", "", ""},
		{"Crouched", "", ""},
		{"Standing", "up", ""},
		{"Pre-", "jumping", ""},
		{"Jumping", "", ""},
		{"Falling,", "initial", ""},
		{"Falling", "", ""},
		{"Landing", "", ""},
		{"Back", "dashing", ""},
		{"Entering", "door", ""},
		{"Exiting", "door", ""},
		{"Taking", "damage", ""},
		{"Taking", "damage,", "crouched"},
		{"Taking", "damage,", "air"},
		{"Knockback", "damage", ""},
		{"Knockback", "upwards", ""},
		{"Knockback", "hitting", "ground"},
		{"Knockback", "fall", ""},
		{"Knockback", "ground", ""},
		{"Knockback", "getting up", ""},
		{"Electro-", "cuted", ""},
		{"Electro-", "cuted", "2"},
		{"Taking", "damage?", ""},
		{"Taking", "damage?", ""},
		{"1st slash,", "wooden,", "ground"},
		{"2nd slash,", "wooden,", "ground"},
		{"3rd slash,", "wooden,", "ground"},
		{"1st slash,", "light,", "ground"},
		{"2nd slash,", "light,", "ground"},
		{"3rd slash,", "light,", "ground"},
		{"1st slash,", "antiquity,", "ground"},
		{"2nd slash,", "antiquity,", "ground"},
		{"3rd slash,", "antiquity,", "ground"},
		{"Crouch", "slash,", "wooden"},
		{"Crouch", "slash,", "light"},
		{"Crouch", "slash,", "antiquity"},
		{"1st slash,", "wooden,", "air"},
		{"2nd slash,", "wooden,", "air"},
		{"3rd slash,", "wooden,", "air"},
		{"1st slash,", "light,", "air"},
		{"2nd slash,", "light,", "air"},
		{"3rd slash,", "light,", "air"},
		{"1st slash,", "antiquity,", "air"},
		{"2nd slash,", "antiquity,", "air"},
		{"3rd slash,", "antiquity,", "air"},
		{"Halo", "Bump,", "ground"},
		{"Halo", "Bump,", "air"},
		{"Nipopo", "Punch", "ground"},
		{"Nipopo", "Punch", "air"},
		{"Daodondo", "", ""},
		{"Gussy", "Kenji", "ground"},
		{"Gussy", "Kenji", "air"},
		{"Jaguar", "Swipe", ""},
		{"Footballer", "", ""},
		{"Footballer,", "sparks", ""},
		{"Big", "Thumb", ""},
		{"Big", "Thumb,", "smoke"},
		{"Big", "Thumb,", "Tokageroh"},
		{"Celestial", "Slash,", "ground"},
		{"Celestial", "Slash,", "air"},
		{"Celestial", "Slash,", "blade"},
		[82] = {"Shikigami,", "ground", ""},
		[83] = {"Shikigami,", "air", ""},
		[90] = {"Michael,", "ground", ""},
		[91] = {"Michael,", "air", ""},
		[91] = {"Michael,", "air", ""},
		[105] = {"Mama,", "transform,", "air"},
		[106] = {"Mama,", "transform,", "ground"},
		[107] = {"Mama,", "recovery", ""},
		[108] = {"Mama,", "running", ""},
		[109] = {"Mama,", "jumping", ""},
		[110] = {"Mama,", "running", "jump"},
		[111] = {"Mama,", "transform", "back"},
		[222] = {"Into The", "Antiquity", ""},
		[230] = {"Homing", "Pendulum,", "straight"},
		[231] = {"Homing", "Pendulum,", "withdraw"},
		[232] = {"Homing", "Pendulum,", "diagonal"},
		[234] = {"Homing", "Pendulum,", "up"},
		[236] = {"Homing", "Pendulum,", "j. strght"},
		[238] = {"Homing", "Pendulum,", "j. diag"},
		[240] = {"Homing", "Pendulum,", "j. up"},
		[242] = {"Homing", "Pendulum,", "attached"},
		[243] = {"Homing", "Pendulum,", "turning"},
		[259] = {"Totem", "Attack,", "summoning"},
		[260] = {"Totem", "Attack,", "charge"},
		[261] = {"Totem", "Attack,", "sparks"},
		[262] = {"Totem", "Attack,", "fire"},
		[263] = {"Totem", "Attack,", "withdraw"}
	}

	local state = memory.read_u16_le(0x35E0, "IWRAM")
	local duration = memory.read_u8(0x35E9, "IWRAM")
	local statetimer = memory.read_u8(0x35DF, "IWRAM")
	local delay = memory.read_u16_le(0x362D, "IWRAM")

	local t = stateText[state]
	if t then
		gui.pixelText(x, y, state .. ":" .. statetimer)
		gui.pixelText(x, y + 7, t[1], color.opaque[1])
		gui.pixelText(x, y + 14, t[2], color.opaque[1])
		gui.pixelText(x, y + 21, t[3], color.opaque[1])
		gui.pixelText(x, y + 28, duration .. ":" .. delay, 0xFFFFFFFF, color.trans[2])
	else
		gui.pixelText(x, y, state .. ":" .. statetimer)
		gui.pixelText(x, y + 7, "NULL!!!", 0xFFC0C0C0)
		gui.pixelText(x, y + 28, duration .. ":" .. delay, 0xFFFFFFFF, color.trans[2])
	end
end

-- Information about our inventory (mediums, souls)
local function InventoryInfo(x, y)
	local leafcount = memory.read_u8(0x2358, "IWRAM")
	local rockcount = memory.read_u8(0x2359, "IWRAM")
	local dollcount = memory.read_u8(0x235A, "IWRAM")

	gui.pixelText(x, y, leafcount, color.opaque[0])
	gui.pixelText(x + 11, y, rockcount, color.opaque[3])
	gui.pixelText(x + 22, y, dollcount, color.opaque[5])
end

-- SK2 rng re-implementation in Lua
local function RngLua(value)
	local high = (value * 0x41C6) % 0x10000
	local low = (value * 0x4E6D) % 0x100000000
	return ((low + high * 0x10000) % 0x100000000) + 0x3039
end

-- Hash table with two fields:
-- first color of background, then color of text
local fancy_color_table = {
	{0xAAFF0000, 0xFFFFFFFF}, --red
	{0xAAFF7F00, 0xFF000000}, --orange
	{0xAAFFFF00, 0xFF000000}, --yellow
	{0xAA00FF00, 0xFF000000}, --green
	{0xAA0000FF, 0xFFFFFFFF}, --blue
	{0xAA4B0082, 0xFFFFFFFF}, --darkpurple
	{0xAA9400D3, 0xFFFFFFFF} --purple
}

local function fancy_colors(value)
	return fancy_color_table[(value % 6) + 1]
end

-- Displays a table of the next X rng values, based on current
-- This function will be called each frame, so globals persist between frames
pastRNG = memory.read_u32_le(0x1DC8, "IWRAM") -- Seed with the initial value from RAM
taken = 0

-- Populate a queue with new RNG values
local function populate(queue, n)
	-- At the start, the queue contains a single value: the current RNG.
	-- That is the 'previous' element, memoized.
	local previous = queue:get()

	-- Fill the rest of the queue:
	-- slot 0 is already filled, so slots 1 to n.
	for i = 1, n do
		-- Compute the current element with the help of the previous one.
		local current = RngLua(previous)
		queue:enqueue(current)
		-- Before we loop, the current element is the previous one.
		previous = current
	end
end
--

--[[ Our update pattern:
1. Look at the element in the tail of the queue.
2. Add an element, updating both the head and tail, since the queue is always full.
3. Look again and repeat.

The queue is always full this way, so we can deal with this in a very fast way. 
It will always loop around.
]] local function consume(
	queue)
	oldest = queue:get() -- Get the oldest value (if zero consumes, the current RNG, if not, the RNG advanced N times)
	newest = queue:get_head() -- and the newest value (RNG advanced n times.)
	-- Advance the queue:
	-- overwrite the oldest value with the advanced new value.
	queue:enqueue(RngLua(newest))
	return oldest -- Relevant RNG.
end

local function RngPredict(ctx, x, y)
	local RNG = memory.read_u32_le(0x1DC8, "IWRAM")
	local queue = ctx.queue
	local size = queue.size

	-- If the queue is empty, populate it
	if queue:empty() then
		queue:enqueue(RNG)
		populate(queue, (size - 1)) -- We already queued one element, the current RNG
	end

	-- Has the RNG advanced?
	if ctx.pastRNG ~= RNG then
		local found = false
		local taken = 0

		-- Take values from the queue, until we find the one matching the new RNG
		-- in this frame.
		while not found and taken < 5 do
			compared_RNG = consume(queue)
			taken = taken + 1
			if compared_RNG == RNG then -- Have we found the RNG yet?
				found = true
			end
		end

		ctx.taken = taken
		-- Update pastRNG
		ctx.pastRNG = RNG
	end

	-- Display on-screen how much the RNG has advanced
	local taken = ctx.taken
	if taken ~= 0 then
		gui.pixelText(x + 33, y, taken, 0xFFFFFFFF, color.trans[6])
		gui.pixelText(x + 33, y + taken * 7, "!", 0xFFFFFFFF, color.trans[2])
	end

	-- Show the queue of RNG values
	for i = 0, (size - 1) do
		value = queue:get_advance() -- Loop through each of the RNG values
		gui.pixelText(
			x,
			y + i * 7,
			string.format("%8x", value),
			fancy_colors(value)[2], -- Returns fancy rainbow colors
			fancy_colors(value)[1]
		) -- More rainbows!
	end
end

-- Draws hitboxes
-- Object size: B4 (180)
local function DrawHitbox(x, y, offset, id)
	local cameraX = memory.read_s32_le(0x1E50, "IWRAM") / 256
	local cameraY = memory.read_s32_le(0x1E54, "IWRAM") / 256

	-- Figure out appropriate pixel values (somewhat hacky)
	local X1, X2 = memory.read_s32_le(0x3664 + offset, "IWRAM") / 256, memory.read_s32_le(0x3668 + offset, "IWRAM") / 256
	local Y1, Y2 = memory.read_s32_le(0x366C + offset, "IWRAM") / 256, memory.read_s32_le(0x3670 + offset, "IWRAM") / 256

	local pixelX1, pixelX2 = X1 - cameraX + 120, X2 - cameraX + 120
	local pixelY1, pixelY2 = cameraY - Y1 + 71, cameraY - Y2 + 71

	local invicibility = memory.read_u16_le(0x3630 + offset, "IWRAM")
	-- Add invicibility counter if invicible
	if invicibility >= 1 then
		gui.drawBox(x + pixelX1, y + pixelY1, x + pixelX2, y + pixelY2, color.opaque[1], color.trans[5])
		gui.pixelText(x + pixelX1, y + pixelY1, invicibility, 0xFFFFFFFF, color.trans[1])
	else
		gui.drawBox(x + pixelX1, y + pixelY1, x + pixelX2, y + pixelY2, color.opaque[5], color.trans[5])
	end

	-- Facing direction
	local fdirection = memory.read_u8(0x3634 + offset, "IWRAM")
	if fdirection == 1 then
		gui.pixelText(x + pixelX1, y + pixelY2 - 7, "<")
	elseif fdirection == 0 then
		gui.pixelText(x + pixelX1, y + pixelY2 - 7, ">")
	else
		gui.pixelText(x + pixelX1, y + pixelY2 - 7, "?")
	end

	-- Raw damage output
	local rawdmg = memory.read_u8(0x3638 + offset, "IWRAM")
	gui.pixelText(x + pixelX1 + 8, y + pixelY2 - 7, rawdmg, 0xFFFFFFFF, color.trans[2])

	-- Health
	local health = memory.read_u16_le(0x3636 + offset, "IWRAM")
	gui.pixelText(x + pixelX1 + 10, y + pixelY1 + 1, health, color.opaque[1])

	-- State, animation and timer information
	if id ~= 0 then
		local state = memory.read_u16_le(0x35E0 + offset, "IWRAM")
		local statetimer = memory.read_u8(0x35DF + offset, "IWRAM")
		local duration = memory.read_u8(0x35E9 + offset, "IWRAM")
		local delay = memory.read_u16_le(0x362D + offset, "IWRAM")
		gui.pixelText(x + pixelX1 + 24, y + pixelY2, state .. ":" .. statetimer)
		gui.pixelText(x + pixelX1 + 24, y + pixelY2 - 7, duration .. ":" .. delay, 0xFFFFFFFF, color.trans[2])
	end
end

local function CamHack()
	local cameraX = memory.read_s32_le(0x1E50, "IWRAM")
	local xpos = memory.read_s32_le(0x3648, "IWRAM")
	memory.write_s32_le(0x1E50, xpos, "IWRAM")
	--gui.pixelText(120, 160 - 14, string.format("Camera X: %d", cameraX), 0xFFFFFFFF, color.opaque[2])
	gui.pixelText(120, 160 - 7, "Camhack active (MAY NOT SYNC)", 0xFFFFFFFF, color.opaque[2])
end

local ctx = {}
ctx.size = 18
ctx.queue = Queue:new {size = ctx.size}
ctx.pastRNG = 0
ctx.taken = 0

-- When we load a state, we will not find a RNG value
-- So emptying the queue and loading it with the new RNG value from the loaded
-- state is needed
-- We also need to make sure
local function resetQueue()
	print("callback: queue reset!")
	ctx.queue = Queue:new {size = ctx.size}
	ctx.pastRNG = memory.read_u32_le(0x1DC8, "IWRAM")
	taken = 0
end
event.onloadstate(resetQueue, "empty queue")

while true do
	for i = 0, max_entities do
		if memory.read_u16_le(0x3636 + i * 180, "IWRAM") ~= 0 then
			DrawHitbox(0, 8, 0 + i * 180, i) -- 0xB4
		end
	end

	RngPredict(ctx, 241, 74, 10)
	MovieInfo(241, 0)
	Separator(241, 22, 38)
	GetYohState(241, 24)
	Separator(241, 61, 38)
	InventoryInfo(241, 63)
	DisplayHud(0, 0)
	--CamHack() -- Comment out to sync, camhack may cause desync

	emu.frameadvance()
end