User File #51685255902414411

Upload All User Files

#51685255902414411 - Donald Land HUD script

Donald_Land.lua
Game: Donald Land ( NES, see all files )
512 downloads
Uploaded 12/6/2018 3:25 PM by Warepire (see all 18)
Fantastic4 DTC7 script
-- FanTAStic 4 Donald Land Lua script.

-- Emulator Abstraction Layer for functions that don't work the same on
-- BizHawk and FCEUX.

-- HACKY: Emulator detection, global because a deeper namespace can't read
--        from a shallower namespace?
is_fceux = nil
if FCEU ~= nil then  -- FCEU unique module, not available on BizHawk
	is_fceux = true
else
	is_fceux = false
end
-- EAL namespace
local EAL = {}
do
	local mem = nil
	local ROM = nil
	local GUI = nil
	local Core = nil
end
-- EAL Memory namespace functions
EAL.mem = {}
do
	function EAL.mem.read_u8(addr)
		if is_fceux then
			return memory.readbyte(addr)
		else
			return memory.read_u8(addr)
		end
	end
	function EAL.mem.read_s8(addr)
		if is_fceux then
			return memory.readbytesigned(addr)
		else
			return memory.read_s8(addr)
		end
	end
	function EAL.mem.read_u16_be(addr)
		if is_fceux then
			return memory.readword(addr + 1, addr)
		else
			return memory.read_u16_be(addr)
		end
	end
	function EAL.mem.read_s16_be(addr)
		if is_fceux then
			return memory.readwordsigned(addr + 1, addr)
		else
			return memory.read_s16_be(addr)
		end
	end
	function EAL.mem.read_s16_be_scatter(hi, lo)
		if is_fceux then
			return memory.readwordsigned(lo, hi)
		else
			local val = memory.read_u8(hi)
			val = bit.lshift(val, 8)
			val = bit.bor(val, memory.read_u8(lo))
			if val > 0x7FFF then
				return val - 0x10000
			else
				return val
			end
		end
	end
	function EAL.mem.read_u16_le(addr)
		if is_fceux then
			return memory.readword(addr)
		else
			return memory.read_u16_le(addr)
		end
	end
end
-- EAL ROM namespace functions
EAL.ROM = {}
do
	-- `addr` is the address as seen from the system bus
	local function get_addr_in_bank(bank, addr)
		local bank_addr = addr - 0x8000
		if addr > 0xC000 then -- high region
			bank_addr = bank_addr - 0x4000
		end
		return bank_addr
	end
	-- `addr` is the address as seen from the system bus
	local function get_offset_in_prg(bank, addr)
		local bank_addr = get_addr_in_bank(bank, addr)
		local offset = (bank * 0x4000) + bank_addr
		return offset
	end
	-- Usage note: `addr` is the address as seen from the system bus
	function EAL.ROM.read_u8(bank, addr)
		local offset = get_offset_in_prg(bank, addr)
		if is_fceux then
			offset = offset + 16 -- FCEUX includes the header
			return rom.readbyte(offset)
		else
			return memory.read_u8(offset, "PRG ROM")
		end
	end

	-- Usage note: `addr` is the address as seen from the system bus
	function EAL.ROM.read_u16_be(bank, addr)
		local offset = get_offset_in_prg(bank, addr)
		if is_fceux then
			offset = offset + 16 -- FCEUX includes the header
			local val = rom.readbyteunsigned(offset)
			val = bit.lshift(val, 8)
			val = bit.bor(val, rom.readbyteunsigned(offset + 1))
			return val
		else
			return memory.read_u16_be(offset, "PRG ROM")
		end
	end

	function EAL.ROM.read_u16_le(bank, addr)
		local offset = get_offset_in_prg(bank, addr)
		if is_fceux then
			offset = offset + 16 -- FCEUX includes the header
			local val = rom.readbyteunsigned(offset + 1)
			val = bit.lshift(val, 8)
			val = bit.bor(val, rom.readbyteunsigned(offset))
			return val
		else
			return memory.read_u16_le(offset, "PRG ROM")
		end
	end
end
-- EAL GUI namespace functions
EAL.GUI = {}
do
	local offsetx = 0
	local offsety = 0

	function EAL.GUI.extend_draw_area(top, bottom, left, right)
		if is_fceux then
			return
		else
			client.SetGameExtraPadding(left, top, right, bottom)
			offsetx = left
			offsety = top
		end
	end

	local textx
	local texty

	function EAL.GUI.set_text_area(x, y)
		textx = x
		texty = y
	end

	function EAL.GUI.print_data(token, value)
		str = string.format("%s : %s", token, tostring(value))
		if is_fceux then
			gui.text(textx, texty, str)
			texty = texty + 10
		else
			gui.drawText(textx + offsetx, texty + offsety,
					str, nil, "blue")
			texty = texty + 20
		end
	end
	function EAL.GUI.print_data2(token, value, value2)
		str = string.format("%s : %s %s", token, tostring(value), tostring(value2))
		if is_fceux then
			gui.text(textx, texty, str)
			texty = texty + 10
		else
			gui.drawText(textx + offsetx, texty + offsety,
					str, nil, "blue")
			texty = texty + 20
		end
	end

	function EAL.GUI.box(x1, x2, y1, y2)
		if is_fceux then
			gui.box(x1, y1, x2, y2)
		else
			gui.drawBox(x1 + offsetx, y1 + offsety,
					x2 + offsetx, y2 + offsety)
		end

	end
end
-- Core namespace
EAL.Core = {}
do
	function EAL.Core.set_lag(is_lag)
		if is_fceux then
			emu.setlagflag(is_lag)
		else
			emu.setislagged(is_lag)
			if is_lag then
				emu.setlagcount(emu.lagcount() + 1)
			end
			tastudio.setlag(emu.framecount(),is_lag)
		end
	end
end

---------------------------------------------------------------------------
---------------------------------------------------------------------------

--19:38 < micro500> if you throw an apple towards the left edge of the map the apple gets completely removed, and I see the code path for that
--20:38 < micro500> it seems like the apples are limited to a Y position of 0x08 or greater
--01:37 < micro500> I'm starting to think that counter 0-0x50 affects the vertical speed of the apple, and when it reaches 0x50 it is falling at full speed
--01:38 < micro500> I found a code path when an apple is thrown while hold down to set that counter to 0x50
--02:03 < micro500> I'm looking at the code path where it checks that "apple count" value
--02:04 < micro500> if your apple count is 0 and you already have an apple out it skips over running a function that I thought was important, but I NOP'd it out and it doesn't prevent apples from being thrown
--02:05 < micro500> so now I'm not sure how this works
--02:13 < micro500> for ViGadeomes: address 2 as far as I know is used as a temporary variable, so its pointless to put in the ram watch
--03:01 < micro500> so the loaded map in ram is larger than I thought
--03:01 < micro500> one block starts at 0x240, and another at 0x330
--03:02 < micro500> if you make the ram watch window large enough to see the area of 0x240-0x3F0 then move across the map you'll see what I mean
--03:19 < micro500> weirdly the powerup box gets picked up before it disappears on screen
--03:38 < micro500> the game assumes you are 2 blocks high, and when checking for point boxes it checks your Y position and your Y position + 16
--03:38 < micro500> so it ends up checking 2 blocks
--03:45 < micro500> looks like the first level data comes from bank 3 0x80d0, 0xC1C0 in the rom file
--03:47 < micro500> correction: 0xC0E0 in the rom file
--04:00 < micro500> looks like the data in 0x240... is for collision/deciding if you should get points/etc
--04:00 < micro500> I think when the game loads more data from the ROM it sends it to the PPU at that time
--04:01 < micro500> if you collect a box it updates the PPU memory then
--04:01 < micro500> when I collect a box (block ID 0x05) it changes in RAM to block id 0x32 (a background tile?), but if I change it back it doesn't put the block back on screen
--04:01 < micro500> so I think it only updates the PPU when it needs to
--04:02 < micro500> you can still collect the box and get points though :)
--04:02 < micro500> not sure if it is important, but I noticed there are some pixels below the timer that flicker
--04:07 < micro500> it seems that if you go forwards, spawn some powerup boxes, then go backwards, unspawn them, then go forwards to where they were they don't spawn again
--04:07 < micro500> so the game remembers not to spawn them again somehow
--04:08 < micro500> I'm guessing somewhere there is a max screen scrolled value to indicate the furthest right you've ever gone?
--04:34 < micro500> oh wtf
--04:34 < micro500> address 0x420 onwards
--04:34 < micro500> they store PPU memory writes there
--04:34 < micro500> addr high byte, addr low byte, value to write, etc
--05:13 < micro500> 0x605 seems to indicate how far into the level you are for use of the blocks
--05:13 < micro500> it seems to count how many blocks you've seen
--05:13 < micro500> and you can change it to a lower number and it will re-render blocks you have de-spawned
--05:25 < micro500> still trying to figure out how the map data is stored
--05:25 < micro500> its...odd
--05:26 < micro500> I found where they decide if the next powerup block should be rendered or not, but the math is a bit crazy
function get_number_apples()
	-- Check if we're carrying 1 or 2 apples, non-0 == 2
	local apples = EAL.mem.read_u8(0x062B) + 1
	if apples ~= 1 then
		apples = 2
	end
	return apples
end


function get_position()
	local onscreen_x = EAL.mem.read_u8(0x0091)
	local x = onscreen_x + get_screen_x_coordinate()
	local subx = EAL.mem.read_u8(0x0092)
	local y = EAL.mem.read_u8(0x0095)
	return x, subx, y
end

function get_speed()
	local x = EAL.mem.read_s16_be(0x0098)
	local xa = EAL.mem.read_s8(0x009A)
	local ya = EAL.mem.read_s8(0x009C)
	local boost = EAL.mem.read_s8(0x00A9)
	return x, xa, ya, boost
end

function get_screen_x_coordinate()
	return EAL.mem.read_u16_le(0x00CC)
end

function get_pickup_boxes_level_addr(level)
	local bank = 3
	local level_addr = EAL.ROM.read_u16_le(bank, 0xB1AA + (level * 2))
	return bank, level_addr
end

function get_level_info()
	local level = EAL.mem.read_u8(0x0051)
	local section = EAL.mem.read_u8(0x0052)
	return level, section
end

function pickup_boxes_hud()
	local level, section = get_level_info()
	local screenx = get_screen_x_coordinate()

	if level < 1 or level > 12 then -- don't try to index outside the array
		return
	end

	local bank, pickup_box_addr = get_pickup_boxes_level_addr(level)

	local data = {}
	local count = 0
	while true do
		data[count] = {}
		local x = EAL.ROM.read_u16_be(bank, pickup_box_addr)
		if x == 0xFFFF then
			break
		end
		data[count]["xpos"] = x
		-- This unk0 seems to be related to height, but I am not sure how...
		-- When y is subtracted with unk0 it makes some boxes appear
		-- more "in place", but hides other boxes with strange IDs...
		-- This value has only been seen to be either 0x90 or 0x70
		-- and only in relation to objects that are "spawned".
		data[count]["unk0"] = EAL.ROM.read_u8(bank, pickup_box_addr+2)
		-- This unk1 is almost guaranteed an x-offset into the screen
		-- while xpos is the screen offset into the level.
		data[count]["unk1"] = EAL.ROM.read_u8(bank, pickup_box_addr+3)
		data[count]["ypos"] = EAL.ROM.read_u8(bank, pickup_box_addr+4)
		data[count]["type"] = EAL.ROM.read_u8(bank, pickup_box_addr+5)


		EAL.GUI.set_text_area(data[count]["xpos"] - screenx + data[count]["unk1"],
					data[count]["ypos"])
		EAL.GUI.print_data("Type", data[count]["type"])

		pickup_box_addr = pickup_box_addr + 6
		count = count + 1
	end
end

-- There are 8 enemy slots in the game
-- TODO: Refactor, make readable.
function get_enemy_and_apple_data()
	local data = {}
	-- The last 2 slots are reserved for apples
	for i=0,7 do
		data[i] = {}
		data[i]["status"] = EAL.mem.read_u8(0x04F3 + i)
		data[i]["xpos"] = EAL.mem.read_s16_be_scatter(0x0543 + i, 0x054B + i)
		data[i]["ypos"] = EAL.mem.read_u8(0x0563 + i)
		data[i]["hitbox_w"] = EAL.mem.read_u8(0x0573 + i)
		data[i]["hitbox_h"] = EAL.mem.read_u8(0x057B + i)
		data[i]["boost"] = EAL.mem.read_s8(0x0583 + i)
		if i < 6 then
			data[i]["type"] = EAL.mem.read_u8(0x04FB + i)
		else -- apples
			data[i]["type"] = nil
		end
	-- TODO, what do the timeouts mean?
	-- The game increments the timeout by 1 each frame until it reaches
	-- 80, or until the apple lands, in which case 80 is set directly
	-- When the apple lands, the explosion countdown begins, it starts
	-- at 100, then drops to 40, then counts down 1 each frame.
		if i > 5 then -- apples
			data[i]["timeout"] = EAL.mem.read_u8(0x04D5 + i - 6)
			data[i]["explosion"] = EAL.mem.read_u8(0x04D7 + i - 6)
		end
		local s = data[i]["status"]
	end
	return data
end

function enemy_hud()
	local screenx = get_screen_x_coordinate()
	local enemy_data = get_enemy_and_apple_data()
	for id,v in ipairs(enemy_data) do
		repeat -- Because Lua 5.1 doesn't even have GOTO!
			-- status enum
			--	0 = Slot empty
			--	1 = ??? (used?)
			--	2 = Enemy is on screen
			--	3 = Enemy is off screen
			if v["status"] == 0 then -- slot empty, don't draw
				break
			end

			EAL.GUI.box(v["xpos"] - v["hitbox_w"] - screenx + 1,
					v["xpos"] + v["hitbox_w"] - screenx - 1,
					v["ypos"] - v["hitbox_h"] + 1,
					v["ypos"] + v["hitbox_h"] - 1)
			EAL.GUI.set_text_area(v["xpos"] - screenx, v["ypos"] + 0x10)
			EAL.GUI.print_data("Boost", v["boost"])

			if v["type"] == nil then -- apples
				EAL.GUI.print_data("Expl.timer", v["explosion"])
			end

			break
		until true
	end
end

function donald_hud()
	local screenx = get_screen_x_coordinate()
	local x, subx, y = get_position()
	local speed, xaccel, yaccel, accel_boost = get_speed()
	local apples = get_number_apples()
	-- Donald's hitbox, hardcoded in the ROM as displacement checks
	-- of the enemy hitbox against Donald's (x/y) coordinate.
	EAL.GUI.box(x - 0x7 - screenx, x + 0x7 - screenx, y - 0xF, y + 0xF)

	EAL.GUI.set_text_area(x - screenx, y + 0x10)
	EAL.GUI.print_data2("X Pos", x, subx)
	EAL.GUI.print_data("Speed", speed)
	EAL.GUI.print_data2("Accel (x,y)", xaccel, yaccel)
	EAL.GUI.print_data("Boost accel", accel_boost)
	EAL.GUI.print_data("Apples", apples)
end

function draw_hidden_lag_warning()
	EAL.GUI.print_data3("0 Speed!")
end

function erase_hidden_lag_warning()
	EAL.GUI.print_data4("0 Speed!")
end

function main()
	local previous_screen_x = 0
	local current_screen_x = get_screen_x_coordinate()

	EAL.GUI.extend_draw_area(0, 50, 200, 200)

	while true do
		enemy_hud()
		donald_hud()
		pickup_boxes_hud()

		if previous_screen_x == current_screen_x then
			EAL.Core.set_lag(true)
		end
		previous_screen_x = current_screen_x

		emu.frameadvance()
		current_screen_x = get_screen_x_coordinate()
	end
end

main()