User File #20619882612631784

Upload All User Files

#20619882612631784 - Metal Force - All in One HUD

Metal Force - All In One HUD.lua
2340 downloads
Uploaded 2/6/2015 2:30 PM by Scepheo (see all 11)
An extensive Lua HUD script for Metal Force. Mostly intended for use with glitchy behavior.
--------------------------------------------------------------------------------
-- Metal Storm (NES)                                                          --
-- Lua massive hud script - v1                                                --
-- By Scepheo                                                                 --
--------------------------------------------------------------------------------


-- Configuration
--------------------------------------------------------------------------------

-- Whether to centre the hud on the player, regardless of camera position
local CenterOnPlayer = false

-- Whether to draw black over the level, for use with CenterOnPlayer
local ClearScreen = false

-- Hitbox colours
local PlayerColour             = 0x00FF00
local EnemyColour              = 0x0000FF
local EnemyProjectileColour    = 0xFF0000
local FriendlyProjectileColour = 0xFFFF00

-- Exit colours
local ScrollColour = 0xFFFFFF
local BlackColour  = 0xFF00FF

-- Block colours
local AirColour      = 0x000000
local WallColour     = 0x00AA00
local NonSolidColour = 0x444444
local LadderColour   = 0xAAAA00
local FallColour     = 0x00FFFF
local DamageColour   = 0xFF0000


-- Drawing helper functions
--------------------------------------------------------------------------------

-- Global scaling variables
local xScale = 0
local yScale = 0
local xOffset = 0
local yOffset = 0

local xResolution = 256
local yResolution = 224

local xCam = 0
local yCam = 0

-- Recalculates scaling values for other drawing functions
function Setup()
	xOffset = client.borderwidth()
	yOffset = client.borderheight() - 16
	xScale = client.bufferwidth() / xResolution
	yScale = client.bufferheight() / yResolution
	
	if CenterOnPlayer then
		xCam = memory.read_s16_le(0x002E) - 128
		yCam = memory.read_s16_le(0x0030) - 112
	else
		xCam = memory.read_u16_le(0x0034)
		yCam = -memory.read_s16_le(0x0036)
	end
	
	if ClearScreen then
		gui.drawRectangle(0, 0, 300, 300, 0xFF000000, 0xFF000000)
	end
end

-- Draws a rectangle at the given in-game x/y coordinates
function DrawRectangle(x, y, width, height, color)

	-- Add alpha channels to the colour
	local borderColor = 0xAA000000 + color
	local fillColor   = 0x55000000 + color
	
	-- Account for camera position
	local dx = x - xCam
	local dy = y - yCam
	
	gui.drawRectangle(dx, dy, width, height, borderColor, fillColor)
end

-- Draws text at the given in-game x/y coordinates
-- More than one string can be given, they will be drawn under each other
function DrawText(x, y, ...)
	
	-- Account for camera position and transform to client space
	local dx = xOffset + (x - xCam) * xScale
	local dy = yOffset + (y - yCam) * yScale
	
	-- Draw all the lines under each other
	for i = 1, arg.n do
		gui.text(dx, dy, arg[i])
		dy = dy + 12
	end
end

-- Draws a hitbox at the given in-game x/y coordinates
function DrawHitbox(x, y, width, height, color)
	DrawRectangle(x - width, y - height, width * 2, height * 2, color)
end



-- Block data
--------------------------------------------------------------------------------

function DrawBlocks()
	
	local xOffset = math.floor(xCam / 16)
	local yOffset = math.floor(-yCam / 16)
	
	local drawOffsetX = xOffset * 16 - xCam
	local drawOffsetY = yOffset * 16 + yCam
	
	local width  = memory.read_u8(0x0353)
	local height = memory.read_u8(0x0354)
	
	memory.usememorydomain("System Bus")
	
	for x = 0, 16 do
		local xBlock = x + xOffset
		
		for y = 0, 15 do
			local yBlock = y + yOffset
			
			local address = bit.band(0x6000 + xBlock * height + yBlock, 0xFFFF)
			local tile = memory.read_u8(address)
			
			local colour = 0x000000
			
			-- Air
			if tile <= 0x5D then
				colour = AirColour
			
			-- Ladders
			elseif tile <= 0x5F then
				colour = LadderColour
			
			-- Non-solid floors
			elseif tile <= 0x6F then
				colour = NonSolidColour
			
			-- Solid floors and walls
			elseif tile <= 0xE7 then 
				colour = WallColour
			
			-- Falling floors
			elseif tile <= 0xF3 then
				colour = FallColour
			
			-- Spikes/lava
			else
				colour = DamageColour
			end
			
			colour = colour + 0x66000000
			
			local dx = x * 16 + drawOffsetX
			local dy =  224 - y * 16 - drawOffsetY
			gui.drawRectangle(dx, dy, 16, 16, colour, colour)
		end
	end
	
	memory.usememorydomain("RAM")
end



-- Level bounds
--------------------------------------------------------------------------------
local Bounds = {}

function ReadBounds()
	Bounds.Left   =  memory.read_s16_le(0x0355) +  14
	Bounds.Bottom = -memory.read_s16_le(0x0357) + 224
    Bounds.Right  =  memory.read_s16_le(0x0359) + 242
    Bounds.Top    = -memory.read_s16_le(0x035B) +  14
end



-- Transition drawing
--------------------------------------------------------------------------------

function DrawExits()
	local left   = ReadTransition(0x035D)
	DrawExit(left, DrawLeft)
	
	local bottom = ReadTransition(0x035F)
	DrawExit(bottom, DrawBottom)
	
	local right  = ReadTransition(0x0361)
	DrawExit(right, DrawRight)
	
	local top    = ReadTransition(0x0363)
	DrawExit(top, DrawTop)
end

function DrawExit(transition, draw)

	-- Draw scrolling transitions
	while (transition.State > 0) do
		draw(transition.Offset, ScrollColour)
		
		transition.Offset = transition.Offset + 2
		transition.State = transition.State - 1
	end
	
	-- Draw black transitions
	while (transition.State < 0) do
		draw(transition.Offset, BlackColour)
		
		transition.Offset = transition.Offset + 2
		transition.State = transition.State + 1
	end
end

function DrawLeft(offset, color)
	local x = Bounds.Left
	local y = -memory.read_s8(0x0355 + offset) * 0x100
	
	DrawRectangle(x - 16, y, 16, 0xFF, color)
end

function DrawBottom(offset, color)
	local x = memory.read_s8(0x0355 + offset) * 0x100
	local y = Bounds.Bottom
	
	DrawRectangle(x, y, 0xFF, 16, color)
end

function DrawRight(offset, color)
	local x = Bounds.Right
	local y = -memory.read_s8(0x0355 + offset) * 0x100
	
	DrawRectangle(x, y, 16, 0xFF, color)
end

function DrawTop(offset, color)
	local x = memory.read_s8(0x0355 + offset) * 0x100
	local y = Bounds.Top
	
	DrawRectangle(x, y - 16, 0xFF, 16, color)
end

function ReadTransition(address)
	local transition = {}
	
	transition.State  = memory.read_s8(address + 0)
	transition.Offset = memory.read_u8(address + 1)
	transition.Position = memory.read_u8(0x0355 + transition.Offset + 0)
	transition.Index    = memory.read_u8(0x0355 + transition.Offset + 1)
	
	return transition
end



-- Enemy data
--------------------------------------------------------------------------------

function DrawEnemies()
	local enemyCount = memory.read_u8(0x0467)
	local dropCount  = memory.read_u8(0x0468)
	local totalCount = enemyCount + dropCount
	
	local drawnCount = 0
	local index = 0
	
	if (enemyCount == 0xFF or dropCount == 0xFF) then
		return
	end
	
	while (drawnCount < totalCount) do
		local drawn = DrawEnemy(index)
		
		if (drawn) then
			drawnCount = drawnCount + 1
		end
		
		index = index + 1
	end
end

function DrawEnemy(number)

	-- Calculate memory offset
	local offset = number * 16

	-- We can skip the enemy drawing if it's disabled
	local status = memory.read_u8(0x03A7 + offset)
	
	if (status == 0) then
		return false
	end
	
	-- Read data (16 bytes, not all know values yet)
	local indexRAM   = memory.read_u8(0x03A8 + offset)
	local x          = memory.read_s16_le(0x03AB + offset)
	local y          = memory.read_s16_le(0x03AD + offset)
	local inv        = memory.read_u8(0x03B1 + offset)
	local phaseTimer = memory.read_u8(0x03B2 + offset)
	local hp         = memory.read_u8(0x03B4 + offset)
		
	-- Fetch enemy table data
	local drop     = memory.read_u8(0x046A + indexRAM)
	local indexROM = memory.read_u8(0x046B + indexRAM)
	
	-- Read size
	memory.usememorydomain("PRG ROM")
	local width  = memory.read_u8(0x34000 + 0x36EE + indexROM * 2)
	local height = memory.read_u8(0x34000 + 0x36EF + indexROM * 2)
	memory.usememorydomain("RAM")
	
	-- Draw hitbox
	DrawHitbox(x, y, width, height, EnemyColour)
	
	local hpText   = "HP:  " .. hp
	local invText  = "INV: " .. inv
	local timeText = "TIM: " .. phaseTimer
	local dropText = ""
	
	-- Only draw drop data if it drops something
	if (drop > 0) then
		
		-- If the drop is known, text, otherwise the number
		if (DropTable[drop] ~= nil) then
			dropText = DropTable[drop]
		else
			dropText = "Drop " .. drop
		end
	end
	
	-- Actually draw the text
	local xText = x - width  + 1
	local yText = y - height + 1
	DrawText(xText, yText, number, hpText, invText, timeText, dropText)
	
	return true
end

-- List of drops
DropTable = 
{
	[16] = "Weapon",
	[17] = "1-UP",
	[18] = "Health",
	[19] = "Power",
	[20] = "Continue"
}



-- Player data
--------------------------------------------------------------------------------

function DrawPlayer()
	local x = memory.read_s16_le(0x002E)
	local y = memory.read_s16_le(0x0030)
	
	local width = memory.read_u8(0x0327)
	local height = memory.read_u8(0x0328)
	
	local state = memory.read_u8(0x0325)
	local ducking = (bit.band(state, 0x08) > 0)
	
	-- Account for ducking
	if ducking then
		y = y + 4
	end
	
	DrawHitbox(x, y, width, height, PlayerColour)
end



-- Enemy projectiles
--------------------------------------------------------------------------------

function DrawEnemyProjectiles()
	local total = memory.read_u8(0x05BB)
	local count = 0
	local index = 0
	
	if (total == 0xFF) then
		return
	end
	
	while (count < total) do
		local drawn = DrawEnemyProjectile(index)
		
		if (drawn) then
			count = count + 1
		end
		
		index = index + 1
	end
end

function DrawEnemyProjectile(index)
	local offset = index * 10
	
	local status = memory.read_u8(0x056B + offset)
	
	if (status == 0) then
		return false
	end
	
	local x = memory.read_s16_le(0x056D + offset)
	local y = memory.read_s16_le(0x056F + offset)
	
	local sizeIndex = status * 2
	
	-- Read size
	memory.usememorydomain("PRG ROM")
	local width  = memory.read_u8(0x34000 + 0x375A + sizeIndex)
	local height = memory.read_u8(0x34000 + 0x375B + sizeIndex)
	memory.usememorydomain("RAM")
	
	DrawHitbox(x, y, width, height, EnemyProjectileColour)
	
	return true
end



-- Friendly projectiles
--------------------------------------------------------------------------------

function DrawFriendlyProjectiles()
	local total = memory.read_u8(0x03A3)
	local count = 0
	local index = 0
	
	if (total == 0xFF) then
		return
	end
	
	while (count < total) do
		local drawn = DrawFriendlyProjectile(index)
		
		if (drawn) then
			count = count + 1
		end
		
		index = index + 1
	end
end

function DrawFriendlyProjectile(index)
	local offset = index * 16
	
	local status = memory.read_u8(0x0373 + offset)
	
	if (status == 0) then
		return false
	end
	
	local x = memory.read_s16_le(0x0375 + offset)
	local y = memory.read_s16_le(0x0378 + offset)
	
	local width  = memory.read_u8(0x0329)
	local height = memory.read_u8(0x032A)
	
	DrawHitbox(x, y, width, height, FriendlyProjectileColour)
	
	return true
end


-- Main loop
--------------------------------------------------------------------------------

while true do
	Setup()
	
	DrawBlocks()
	
	DrawEnemies()
	DrawPlayer()
	DrawEnemyProjectiles()
	DrawFriendlyProjectiles()
	
	ReadBounds()
	DrawExits()
	
	-- Bounds are stored for camera, converted to character position for
	-- convenience.
	gui.text(150, 12, "Level Bounds")
	gui.text(150, 26, "Left:   " .. Bounds.Left   .. "(" .. memory.read_s8(0x035D) .. ")")
	gui.text(150, 40, "Right:  " .. Bounds.Right  .. "(" .. memory.read_s8(0x0361) .. ")")
	gui.text(150, 54, "Top:    " .. Bounds.Top    .. "(" .. memory.read_s8(0x0363) .. ")")
	gui.text(150, 68, "Bottom: " .. Bounds.Bottom .. "(" .. memory.read_s8(0x035F) .. ")")
	
	emu.frameadvance()
end