This was the script we developed in the DTC. Has hitboxes, position, and speed. And a few random things. This was my version, without the side-radar.
--SNES Ghoul Patrol
assert(memory.usememorydomain("WRAM"))
local R1u , R1s = memory.read_u8 , memory.read_s8
local R2u , R2s = memory.read_u16_le , memory.read_s16_le
local R4u , R4s = memory.read_u32_le , memory.read_s32_le
local function R3u(a) return R1u(a) + R2u(a+1)*0x100 end
local Tx= gui.pixelText
local Box= gui.drawRectangle
local R2x = function(v) return string.format("%04X",R2u(v)) end
-------------------------------------------------------------------------------
local FauxRAMWatch= {
-------------------------------------------------------------------------------
{addr=0x0A6A,f=R1u,clr=0xFFFFFFFF}, --Run timer
{addr=0x0A72,f=R1u,clr=0xFFFFFFFF}, --Dash timer
{addr=0x0A56,f=R1u,clr=0xFFFFFF00}, --Slide timer
{addr=0x0A22,f=R2s,clr=0xFFFFFFFF}, --Height
{addr=0x0028,f=R2x,clr=0xFF00FFFF}, --RNG
{addr=0x0024,f=R2u,clr=0xFFFFFFFF}, --Frame count
{addr=0x0A4E,f=R2s,clr=0xFF0040FF}, --Invincibility
{addr=0x7BFA,f=R1u,clr=0xFFFF8000}, --Boss(?) (temp)
{addr=0x1F49,f=R2s,clr=0xFFFF8000}, --Boss HP
{addr=0x1F45,f=R2x,clr=0xFFFF8000}, --Boss ?
{addr=0x715C,f=R1s,clr=0xFFFF8000}, --Boss ?
}
--*****************************************************************************
local function HexVal(v)
--*****************************************************************************
--Just so we see negative -1 in hex as -1 and not FFFFFFFF.
local s= "+"
if v < 0 then s= "-"; v= -v end
return s .. string.format("%X",v)
end
--*****************************************************************************
local function FetchCam() return R2s(0x1DDB), R2s(0x1DDD) end
--Returns two values. Best catch it with CamX,CamY=FetchCam() or something.
--*****************************************************************************
local function SynthesizedPos(v)
--*****************************************************************************
-- ###.#.## format.
-- ###.-.-- Tile position
-- ---.#.-- Pixel position within that tile
-- ---.-.## Subpixel position
local s= string.format("%3d",math.floor(v / 0x80000))
v= v%0x80000
s= s .. "." .. math.floor(v/0x10000) .. "."
v= (v%0x10000) * 100 / 0x10000
return s .. string.format("%02d",math.floor(v))
end
--*****************************************************************************
local function PxTxRight(x,y,t,c1,c2,f,sn)
--*****************************************************************************
--Right-justifies gui.pixelText based on a constant width.
x= x - string.len(t)*4
gui.pixelText(x,y,t,c1,c2,f,sn)
end
--*****************************************************************************
local function SplitColor(x,y,s,c1,c2)
--*****************************************************************************
--Shades the last four characters in grey, by default.
--This is bugged, as I forgot gui.text is left-justified. I'll fix later.
Tx(x ,y,string.sub(s,1,-5),c1 or 0xFFFFFFFF)
Tx(x+16,y,string.sub(s,-4) ,c2 or 0xFFC0C0C0)
end
--*****************************************************************************
local function PlayerProjectiles(CamX, CamY)
--*****************************************************************************
--Tries to get at the five projectiles somewhere in memory.
for i= 0, 9 do
--Someday, an if block to check if the projectile exists...
local o= i*2 --offset
local X, Y= R2u(0x0072F2+o)-CamX, R2u(0x007392+o)-CamY
Box(X-4,Y-8,9,9,0xFFFFFFFF)
--Tx(2,50+8*i,R2u(0x0072F2+o) .. " " .. R2u(0x007392+o) .. " " .. R2u(0x007342+o))
local H= R2u(0x007342+o)
PxTxRight(X-4,Y-10,H+5)
PxTxRight(X-4,Y+ 2,H-5)
end
Box(4,4,5,5,0xFFFFFFFF)
end
local Scale = {
Factor = 1.00,
Adj = 0.25,
KeyUp = "PageUp",
KeyDown= "PageDown",
Left = 0,
Right = 256,
Top = 0,
Bottom = 224
}
--*****************************************************************************
local function AdjustScaling(Keys)
--*****************************************************************************
--Adjust stuff.
if Keys[Scale.KeyUp] then
Scale.Factor= Scale.Factor + Scale.Adj
elseif Keys[Scale.KeyDown] then
Scale.Factor= math.max(Scale.Factor - Scale.Adj, 1)
else --Sneak in an abort to the calcs this way.
return
end
--Adjust where the perimeter is
Scale.Left = math.floor(128 - 128/Scale.Factor)
Scale.Right = 256-Scale.Left
Scale.Top = math.floor(112 - 112/Scale.Factor)
Scale.Bottom= 224-Scale.Top
--Report the new scaling
Tx(128,112,string.format("%5.2f",Scale.Factor))
end
--*****************************************************************************
local function Hitboxes(CamX, CamY)
--*****************************************************************************
local Count= R2u(0x0000A8)
if Count > 80 then return end --Panic
if Scale.Factor > 1 then
gui.drawBox(Scale.Left,Scale.Top,Scale.Right,Scale.Bottom,0x80FFFFFF,0)
end
for i= Count, 0, -2 do
local ptr= R2u(0x1497+i)
local v0E = R2u(ptr+0x0E)
if (v0E ~= 0) then
local Left = R2u(ptr+0x02) - R2u(ptr+0x14)
local Right = R2u(ptr+0x02) + R2u(ptr+0x14)
local Up = R2u(ptr+0x06) - R2u(ptr+0x18)
local Down = R2u(ptr+0x06)
local H_Plus = R2u(ptr+0x04) + R2u(ptr+0x16)
local H_Minus= R2u(ptr+0x04)
local ID = R2u(ptr+0x0C)
local X1= math.floor((Left-CamX) /Scale.Factor + Scale.Left)
local X2= math.floor((Right-CamX)/Scale.Factor + Scale.Left)
local Y1= math.floor((Up-CamY) /Scale.Factor + Scale.Top)
local Y2= math.floor((Down-CamY) /Scale.Factor + Scale.Top)
gui.drawBox(X1,Y1,X2,Y2,0xFFFF4000,0x20FF4000)
if ID < 0x2C then
local DataPtr= R2u(0x808381+ID,"System Bus")
-- PxTxRight(X1,Y1-6,string.format("%04X",DataPtr))
-- PxTxRight(X1,Y1-6,R2s(DataPtr+0x2A))
-- PxTxRight(X1,Y2 ,R4s(DataPtr+0x38))
PxTxRight(X1,Y1-6,H_Plus)
PxTxRight(X1,Y2 ,H_Minus)
else
PxTxRight(X1,Y1-6,string.format("%02X",ID),0xFFFFFF00)
PxTxRight(X1,Y2 ,Down,0xFFFFFF00)
end
end
end
end
--Constants
local size= 0x10000
--0x79D4 - Diagonal cap
--0x8000 - Straight cap
--0x9998 - Straight acceleration
--0x6C10 - Diagonal acceleration
local Orange= 0xFFFF8000
local White = 0xFFFFFFFF
local Blue = 0xFF0060FF
local Grey = 0xFFC0C0C0
local DashSpeedTable= {
{v=0x0000, s=Orange, d=Blue }, --Hand-calculated the thresholds.
{v=0x0DC4, s=Orange, d=Orange}, --Identifies good speeds to try Dash Mode
{v=0x6668, s=Blue , d=Orange}, --trickery with.
{v=0x8001, s=Grey , d=Orange},
{v=0x93F0, s=Grey , d=Blue },
{v=0xA1B4, s=Grey , d=Grey },
{v=0xCCD0, s=Blue , d=Grey },
{v=0xE668, s=Orange, d=Grey },
{v=0xFA58, s=Orange, d=Blue }
}
--*****************************************************************************
local function SpeedDashColors(spd) --Expects full speed value
--*****************************************************************************
if spd < 0 then spd= -spd end
spd= spd%0x10000
for i= #DashSpeedTable, 1, -1 do
local SpeedCheck= DashSpeedTable[i]
if spd >= SpeedCheck.v then
return SpeedCheck.s, SpeedCheck.d
end
end
return 0xFFFF00FF, 0xFFFF00FF --This should not happen. Purple's my error.
end
--*****************************************************************************
local function SpeedColors(spd)
--*****************************************************************************
--Just trying to pack Mittenz' colors in a nice function.
spd= spd/size
--BackSlide ready!
if (spd < -2) or (spd >= 3) then return 0xFF00FF00, 0xFF00CF00 end
--Basically no speed.
if (spd > -1) and (spd < 1) then return 0xFFFF0000, 0xFFC00000 end
--Somewhere in between
return nil, nil --Use default colors
end
-------------------------------------------------------------------------------
--*****************************************************************************
local function RNG_SpawnPos(A) return bit.band(A,0x0F) end
local function RNG_Sign(A) if A >= 0x7F then return "-" end; return "+" end
--*****************************************************************************
local CallerIDs= {
[0x83851B]= {name="Spwn_Hpos" , fn=RNG_SpawnPos },
[0x838523]= {name="Spwn_Vpos" , fn=RNG_SpawnPos },
[0x83852B]= {name="Spwn_Hsign", fn=RNG_Sign },
[0x83853C]= {name="Spwn_Vsign", fn=RNG_Sign },
}
local RNG_Calls= {}
local RequestPause= false
--*****************************************************************************
local function RNG_Call()
--*****************************************************************************
--Table info:
-- caller = Address of the detected JSL
-- A = Accumulator when RNG function ends
-- clr = color to display line
-- name = Just a name to show
-- val = Value that the caller wanted from the RNG
local T= {}
local Stack= emu.getregister("S")
T.caller= R3u(Stack+1) - 3
T.A= emu.getregister("A")
local CallerInfo= CallerIDs[T.caller]
if not CallerInfo then --We don't know who it is.
T.name= ""
T.clr= 0xFFFFFFFF --White
T.val= ""
else
T.name= CallerInfo.name or ""
T.clr= 0xFFFFFF00 --Yellow
T.val= CallerInfo.fn(T.A)
end --Yellow if we know.
table.insert(RNG_Calls,T)
end
event.onmemoryexecute(RNG_Call,0x80C4E4) --RTL, so we can report the A.
--*****************************************************************************
local function TerrainRadar(x,y , px,py)
--*****************************************************************************
--Just a silly little experiment. No screen edge check, though.
--Screen location for drawing, then pixel location for radar to read.
local X_tile= math.floor(px/8)
local Y_tile= math.floor(py/8)
for yT = -6, 5 do
local RowPtr= R2u(0x42C8 + (Y_tile+yT)*2)
for xT = -6, 6 do
local Tile= R2u(0x010000 + RowPtr + (X_tile+xT)*2)
local TileProp= R2u(0x58C8 + (Tile%0x400)*2)
if TileProp%2 == 1 then --It's a wall!
local Height= bit.band(TileProp,0x7000)
if Height == 0 then
Tx(x+xT*6,y+yT*6,"X")
else
Tx(x+xT*6,y+yT*6,string.format("%X",Height/0x1000))
end
elseif bit.band(TileProp,0x0008) ~= 0 then --Pits of DOOM!
Tx(x+xT*6,y+yT*6,"P",0xFFFF8000)
elseif bit.band(TileProp,0x0100) ~= 0 then --Splashy splash
Tx(x+xT*6,y+yT*6,"W",0xFF00FFFF)
end
end
end
Box(x-6,y-6,16,12,0xC0FFFFFF,0x40FF4000)
end
--*****************************************************************************
local function PlayerHUD()
--*****************************************************************************
--All sorts of basic data here.
--X axis
local pos= R2u(0x0A2E)*size+R2u(0x0A2A)
SplitColor( 6, 2,string.format("%8X",pos))
Tx( 40, 2,SynthesizedPos(pos))
local spd= R4s(0x0A32)
local c1, c2= SpeedColors(spd)
SplitColor( 6, 8,string.format("%8s",HexVal(spd)),c1,c2)
Tx( 40, 8,string.format("%+8.2f",spd/size))
local ClrS, ClrD= SpeedDashColors(spd)
Tx( 72, 2,"S",ClrS)
Tx( 72, 8,"D",ClrD)
--Y axis
pos= R2u(0x0A3A)*size+R2u(0x0A36)
SplitColor( 81, 2,string.format("%8X",pos))
Tx(115, 2,SynthesizedPos(pos))
spd= R4s(0x0A3E)
c1, c2= SpeedColors(spd)
SplitColor( 81, 8,string.format("%8s",HexVal(spd)),c1,c2)
Tx(115, 8,string.format("%+8.2f",spd/size))
ClrS, ClrD= SpeedDashColors(spd)
Tx(148, 2,"S",ClrS)
Tx(148, 8,"D",ClrD)
--Height
pos= R2u(0x0A22)*size+R2u(0x0A20)
SplitColor(156, 2,string.format("%8X",pos))
Tx(190, 2,SynthesizedPos(pos))
spd= R4s(0x0A26)
SplitColor(156, 8,string.format("%8s",HexVal(spd)))
Tx(190, 8,string.format("%+8.2f",spd/size))
TerrainRadar(200,180 , R2u(0x0A2E),R2u(0x0A3A))
--Inventory
Tx( 2, 200, string.format("%03X %03X %03X %03X", R2u(0x1D21), R2u(0x1D1F), R2u(0x1D23), R2u(0x1D29)))
Tx( 2, 208, string.format("K:%X +:%X R:%X B:%X G:%X ?:%X F:%X", R2u(0x1D5D), R2u(0x1D71), R2u(0x1D61), R2u(0x1D63), R2u(0x1D65), R2u(0x1D69), R2u(0x1D67)))
end
--*****************************************************************************
local function RAMScan()
--*****************************************************************************
--While it's nice having RAM Watch, I like my scripty ideas.
--Decimal only. The list should be at the top of this script.
for i= 1, #FauxRAMWatch do
local Watch= FauxRAMWatch[i]
local Value= Watch.f(Watch.addr)
PxTxRight(255,8*(i-1),Value,Watch.clr)
end
end
--*****************************************************************************
local function HandleRNG(x,y)
--*****************************************************************************
while #RNG_Calls ~= 0 do
local C= table.remove(RNG_Calls,1)
Tx(x,y,string.format("%06X:%02X %s %s",C.caller, C.A, C.val, C.name),C.clr)
y= y+7
end
end
--*****************************************************************************
local function BasicHUD()
--*****************************************************************************
local CamX, CamY= FetchCam()
--Screen stuff
PlayerProjectiles(CamX,CamY)
Hitboxes(CamX, CamY)
--Have this on the bottom so boxes don't mess with our HUD.
PlayerHUD()
RAMScan()
HandleRNG( 2, 30)
--Boss HP quickie
Tx(64,200,math.ceil(R2u(0x1F49)/17),0xFFFF8000)
end
--emu.setislagged
--tastudio.setlag
--emu.framecount
local OldEmuFrame, OldGameFrame= -2, -2
--*****************************************************************************
local function LagTag()
--*****************************************************************************
local EmuFrame, GameFrame= emu.framecount(), R2u(0x0024)
if EmuFrame == OldEmuFrame+1 then
if GameFrame ~= OldGameFrame+1 then
emu.setislagged()
tastudio.setlag(EmuFrame,true)
end
end
OldEmuFrame, OldGameFrame= EmuFrame, GameFrame
end
--*****************************************************************************
while true do
--*****************************************************************************
local GimmeDaKeys= input.get()
AdjustScaling(GimmeDaKeys)
BasicHUD()
LagTag()
emu.frameadvance();
end