I wanted to investigate that sprite disappearance bug, so I I made script that shows npcs. Their X/Y/ID is 2 bytes, but split apart in terms of 0x10 for some reason:
Download KirbyTiltnTumble.luaLanguage: lua
memory.usememorydomain("System Bus")
client.SetGameExtraPadding(0, 0, 200, 50)
local Addresses =
{
Stage = 0xC194,
start_npc_id = 0xC2E3,
start_npc_x = 0xC3D3,
start_npc_y = 0xC403,
start_npc_id2 = 0xC5A3,
Camera_Y = 0xFF90,
Camera_X = 0xFF91,
X = 0xFFA5, --3 bytes, big endian
Y = 0xFFA8, --3 bytes, big endian
Z = 0xFFAB, --2 bytes
}
local box = gui.drawBox
local text
local read8
local read16
local read24
local read32
console.clear()
if vba then
text = gui.text
read8 = memory.readbyteunsigned
read16 = memory.readwordunsigned
read32 = memory.readlongunsigned
else
text = gui.pixelText
memory.usememorydomain("System Bus")
read8 = memory.read_u8
read16le = memory.read_u16_le
read16be = memory.read_u16_be
read24be = memory.read_u24_be
read32 = memory.read_u32_le
end
local npcs = {
[0] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[1] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[2] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[3] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[4] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[5] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[6] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[7] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[8] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[9] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[10] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[11] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[12] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[13] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[14] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0},
[15] = {x = 0, y = 0, id = 0, id2 = 0, x_address = 0, y_address = 0, id_address = 0, id2_address = 0}
}
for i = 0, 15 do
npcs[i].x_address = Addresses.start_npc_x + i
npcs[i].y_address = Addresses.start_npc_y + i
npcs[i].id_address = Addresses.start_npc_id + i
--behavior related? For instance orbservers both use 14, but different this value that determines behavior
npcs[i].id2_address = Addresses.start_npc_id2 + i
end
function update_npcs()
for i = 0, 15 do
npcs[i].x = (read8(npcs[i].x_address) << 8) + read8(npcs[i].x_address + 0x10)
npcs[i].y = (read8(npcs[i].y_address) << 8) + read8(npcs[i].y_address + 0x10)
npcs[i].id = (read8(npcs[i].id_address) << 8) + read8(npcs[i].id_address + 0x10)
npcs[i].id2 = read8(npcs[i].id2_address)
end
end
function draw_npc()
--[[
IDs are strange, It's 2 bytes, separate by 0x10.
C2E3/C2F3
C2E4/C2F4
C2E5/C2F5
C2E6/C2F6
C2E7/C2F7
seems to be for sparkles, but if they're all taken it would use a latter slot
Invisible sign at 1-1 at start is C2F0/C300
But 1st object seems to be from C2EF/C2FF
Last one seems to be C2F2/C302? so 16 objects?
X/Y is the same format, 2 bytes big endian
]]--
local text_x = 160
local text_y = 0
local cam_x = read8(Addresses.Camera_X)
local cam_y = read8(Addresses.Camera_Y)
local kirby_x = read16be(Addresses.X) --to subtract
local kirby_y = read16be(Addresses.Y) --to subtract
-- local npc_x = (read8(Addresses.npc1_X1) << 8) + read8(Addresses.npc1_X1+0x10)
-- local npc_y = (read8(Addresses.npc1_X1+0x30) << 8) + read8(Addresses.npc1_X1+0x40)
text(text_x, text_y, "Kirby")
text_y = text_y + 8
text(text_x, text_y, "X: " .. kirby_x .. "Y: " .. kirby_y)
text_y = text_y + 8
text(text_x, text_y, "NPC")
-- gui.drawRectangle(npc_cam_x-14, npc_cam_y-16, 20, 19, "red") --Kirby
update_npcs()
for i = 0, 15 do
text_y = text_y + 8
text(text_x, text_y, string.format("%d (%d %d) %d X: %d Y: %d",i,read8(npcs[i].id_address), read8(npcs[i].id_address + 0x10),npcs[i].id2 ,npcs[i].x,npcs[i].y))
local npc_cam_x = cam_x + (npcs[i].x - kirby_x)
local npc_cam_y = cam_y + (npcs[i].y - kirby_y)
text(npc_cam_x, npc_cam_y, i)
end
-- local npc_cam_x = cam_x + (npc_x - kirby_x)
-- local npc_cam_y = cam_y + (npc_y - kirby_y)
end
while true do
local text_x = 160
local text_y = 0
-- local x = (read8(Addresses.npc1_X1) << 8) + read8(Addresses.npc1_X2)
local x = read24be(Addresses.X)
-- local x = read16(Addresses.X)
local y = read24be(Addresses.Y)
local z = read16be(Addresses.Z)
local cam_x = read8(Addresses.Camera_X)
local cam_y = read8(Addresses.Camera_Y)
-- text(text_x, text_y, "Kirby")
text_y = text_y + 8
-- text(text_x, text_y, "X: " .. x .. "Y: " .. y .. "Z: " .. z)
draw_npc()
emu.frameadvance()
end
Thanks Blazephlozard for the original script's X/Y addresses, which I adapted to use read24 instead. ID 1 is the sprite. ID 2 is behavior related? It's most obvious when you have things like Warp stars or Orbservers which share an id, but use different ID2 for behavior.
I also investigated CartRAM so I could unlock stuff faster:
https://docs.google.com/spreadsheets/d/1wLb6DzJ8UiFrRjaEjT3BG5jmr5Lt7FtNsg0-MbmH9FM/edit?usp=sharing
This turns out to be incredibly small. The percent counter is literally a number, and the way it works is that after you beat every single stage and grabbed their red star, the game clears out all stage complete flags, then sets 0050 in CartRam to 1 to set as hard mode. This allows the game to keep percentage while forcing you to beat everything again. Stage select is very simple. The game allows you to select stage if stage 8-4 is beaten. I believe that should be the CartRam address 004E. If that bit flag is set to 1, the game allows stage select, even if you never beaten anything else at all.