User File #38508373147113065

Upload All User Files

#38508373147113065 - Zook Man ZX4 - Terrain Tiles painter

ZM_TileOverlay.lua
886 downloads
Uploaded 4/22/2017 5:13 AM by FatRatKnight (see all 245)
Shh... Secrets. Script #1, intended to be run first.
This script paints an overlay on the screen, showing what type each tile is, in convenient color coding. This is what we use to know exactly when air ends and walls begin. Seriously, there are spikes that aren't there in Stage 4, walls that aren't there in the sixth stage part of END, and plenty of floor clips that this script makes apparent very quickly. Also be sure to look for backward slopes in the intro and Stage 4, too.
Note that the script takes a few seconds to load all the tiles into lua tables, as this method means I won't have to do several memory reads to get the right pointer, adjust it, then fetch the right tile. The script is much slower in runtime without this loading step. Also, this required some ROM diving, and I had better hope this thing is useful!
--memory.usememorydomain("ROM")
local R4u= memory.read_u32_le
local R4s= memory.read_s32_le
local R2u= memory.read_u16_le
local R2s= memory.read_s16_le
local R1u= memory.read_u8

local ROMmin,ROMmax= 0x08000000, 0x08000000+memory.getmemorydomainsize("ROM")

--*****************************************************************************
local function FetchAddrDomainGBA(a)
--*****************************************************************************
--I am furious at the design away from the bus. It used to exist! Why remove?
--I do not want to code in removing offsets to pointers every time I read one.
--This function was made because I insist on full pointers. Has only what I know.

    if     (a >= 0x02000000) and (a < (0x02000000+memory.getmemorydomainsize("EWRAM"))) then
        return a-0x02000000, "EWRAM"
    elseif (a >= 0x03000000) and (a < (0x03000000+memory.getmemorydomainsize("IWRAM"))) then
        return a-0x03000000, "IWRAM"
    elseif (a >= 0x08000000) and (a < (0x08000000+memory.getmemorydomainsize("ROM"))) then
        return a-0x08000000, "ROM"
    else
        error(string.format("Unknown address %08X", a),1)
    end
end

local Maps= {}

--*****************************************************************************
local function LoadMaps()
--*****************************************************************************
--This function tracks down every stage, every segment in each stage, every
--block in each segment, and every tile in each block. Then it stitches
--together the tiles as it would fit in the block.

  for Stage= 0, 16 do
    local StageMaps= {} --Construct new table

    local BlockSetPtr= R4u(0x3CD86C + 4*Stage,"ROM")
    local SegmentBase= R4u(0x3CD8B0 + 4*Stage,"ROM")
    local Segment= 0
    while true do
      local SegmentPtr= R4u(FetchAddrDomainGBA(SegmentBase + Segment*4))
      if (SegmentPtr < ROMmin) or (SegmentPtr >= ROMmax) then break end

      local SegX, SegY= R2s(FetchAddrDomainGBA(SegmentPtr)), R2s(FetchAddrDomainGBA(SegmentPtr+2))
      SegmentPtr= SegmentPtr+4

      local SegmentMap= {}
      for y= 0, SegY-1 do
        for x= 0, SegX-1 do
          local BlockIndex= R2u(FetchAddrDomainGBA(SegmentPtr + (y*SegX + x)*2))
          local BlockPtr= R4u(FetchAddrDomainGBA(BlockSetPtr + 4*BlockIndex))
          for yy= 0, 31 do
            for xx= 0, 31 do
              local i= xx + x*32 + yy*SegX*32 + y*SegX*1024
              SegmentMap[i]= R1u(FetchAddrDomainGBA(BlockPtr + 2 + xx + 32*yy))
            end
          end
        end
      end

      StageMaps[Segment]= SegmentMap
      Segment= Segment+1
    end

    Maps[Stage]= StageMaps --Stash what we've got!
    print("Loaded Stage " .. Stage)
  end
end
LoadMaps()

--x - X location on screen. Might be negative./
--y - Y location on screen.
--i - Block index, in case you put the function in multiple places

local function NullFn() return end

local DefaultColors= {
[0x00]= 0xFFFFFFFF,
[0x10]= 0xFF00FF00,
[0x60]= 0xFFFFFF00,
[0x70]= 0xFF00FFFF
}
--*****************************************************************************
local function Tile_Default(x,y,i)
--*****************************************************************************
  local clr= DefaultColors[i - i%0x10] or 0xFFFF00FF
  gui.pixelText(x,y,string.format("%X",i%0x10),clr)
end

local BlockColors= {
[0]=0x00000000, -- Open air
    0xC0FFFFFF, -- Floor
    0xC0FF2000, -- Spikes
    0xC0000000, -- Walls (basic)
    0x00000000, -- Unused
    0xC000FFFF, -- Walljump

[0x0A]=0x80FFFF00, --Slope magnet downward from above
[0x0B]=0x80FFFF00, --Slope magnet upward from below

[0x14]=0x8040A000, --Ladder top
[0x15]=0x8040FF40, --Ladder mid
[0x16]=0x8040A000, --Ladder bottom

[0x17]=0xC00000FF, --Save Platform mid
[0x18]=0xC00000FF, --Save Platform end

[0x19]=0xC08000FF, --Capsule Head
[0x1A]=0xC08000FF, --Capsule Body
[0x1B]=0xC08000FF, --Capsule Leg
[0x1C]=0xC08000FF, --Capsule Arm

[0x1F]=0xC08000FF, --Autoscroll ender tiles
}
--*****************************************************************************
local function Tile_Blocks(x,y,i)
--*****************************************************************************
  local clr= BlockColors[i] or 0xFFFF00FF
  local bclr= bit.band(clr/2,0xFF000000) + bit.band(clr,0x00FFFFFF)
  gui.drawRectangle(x,y,7,7,clr,bclr)
end

local BreakColors= {
  [0x60]=0x60FF0000, [0x61]=0x60FFFF00, [0x62]=0x6000FF00, [0x63]=0x6000FFFF,
  [0x64]=0x600000FF, [0x65]=0x60FF00FF, [0x66]=0x60FFFFFF, [0x67]=0x60000000,
  [0x68]=0x6080FF00, [0x69]=0x6000FF80, [0x6A]=0x600080FF, [0x6B]=0x608000FF,
  [0x6C]=0x60FF0080, [0x6D]=0x60FF8000, [0x6E]=0x60808080, [0x6F]=0x60800080,
  [0x70]=0x60808000, [0x71]=0x60008080, [0x72]=0x60FF80FF, [0x73]=0x60FFFF80,
  [0x74]=0x6080FFFF, [0x75]=0x6080FF80, [0x76]=0x608080FF
}
--*****************************************************************************
local function Tile_Break(x,y,i)
--*****************************************************************************
  local clr= BreakColors[i] or 0xC0FF00FF
  gui.drawRectangle(x,y,7,7,0,clr)
end

local SlopeHeights= {
  [0x06]={L=7,R=4},[0x07]={L=3,R=0},[0x08]={L=0,R=3},[0x09]={L=4,R=7},
  [0x0C]={L=7,R=6},[0x0D]={L=5,R=4},[0x0E]={L=3,R=2},[0x0F]={L=1,R=0},
  [0x10]={L=0,R=1},[0x11]={L=2,R=3},[0x12]={L=4,R=5},[0x13]={L=6,R=7}
}
--*****************************************************************************
local function Tile_Slope(x,y,i)
--*****************************************************************************
  gui.drawRectangle(x,y,7,7,0x40808000,0x40808000)
  local h= SlopeHeights[i]; if not h then return end
  local y1,y2= h.L+y,h.R+y
  gui.drawLine(x,y1,x+7,y2,0xFFFFFFFF)
end

--[[
00 Air            01 Floor          02 Spikes         03 Solid
04 (unused)       05 Walljump       06 / Steep1       07 / Steep2
08 \ Steep1       09 \ Steep2       0A Magnet down    0B Magnet up
0C / Gentle1      0D / Gentle2      0E / Gentle3      0F / Gentle4
10 \ Gentle1      11 \ Gentle2      12 \ Gentle3      13 \ Gentle4
14 Ladder Top     15 Ladder         16 Ladder Bottom  17 Save platform
18 Save platform  19 Capsule Head   1A Capsule Body   1B Capsule Leg
1C Capsule Arm    1D (unused)       1E (unused)       1F Autoscroll end

60 - 76 Breakable blocks
]]--

local Patterns= {
[0]=NullFn     ,Tile_Blocks,Tile_Blocks,Tile_Blocks, --Air,Floor,Spike,Solid
    nil        ,Tile_Blocks,Tile_Slope ,Tile_Slope , --nil,Wj,Sslope /
    Tile_Slope ,Tile_Slope ,Tile_Blocks,Tile_Blocks, --Sslope \,magnet
    Tile_Slope ,Tile_Slope ,Tile_Slope ,Tile_Slope , --Gslope /
    Tile_Slope ,Tile_Slope ,Tile_Slope ,Tile_Slope , --Gslope \
    Tile_Blocks,Tile_Blocks,Tile_Blocks,Tile_Blocks, --Ladder, sPlatform
    Tile_Blocks,Tile_Blocks,Tile_Blocks,Tile_Blocks, --sPlatform, Capsule
    Tile_Blocks,nil        ,nil        ,Tile_Blocks, --Capsule,nil,nil,AS end

  [0x60]=Tile_Break, [0x61]=Tile_Break, [0x62]=Tile_Break, [0x63]=Tile_Break,
  [0x64]=Tile_Break, [0x65]=Tile_Break, [0x66]=Tile_Break, [0x67]=Tile_Break,
  [0x68]=Tile_Break, [0x69]=Tile_Break, [0x6A]=Tile_Break, [0x6B]=Tile_Break,
  [0x6C]=Tile_Break, [0x6D]=Tile_Break, [0x6E]=Tile_Break, [0x6F]=Tile_Break,
  [0x70]=Tile_Break, [0x71]=Tile_Break, [0x72]=Tile_Break, [0x73]=Tile_Break,
  [0x74]=Tile_Break, [0x75]=Tile_Break, [0x76]=Tile_Break
}

--*****************************************************************************
local function TileOverlay2()
--*****************************************************************************
  local Stage, Segment= R4s(0x152C,"IWRAM"),R4s(0x1530,"IWRAM")
  local Map= Maps[Stage]; if not Map then return end
  Map= Map[Segment]     ; if not Map then return end

--Conveniently, the game has width and height loaded in RAM.
  local SegX, SegY= R4s(0x153C,"IWRAM"), R4s(0x1540,"IWRAM")
  if (SegX < 1) or (SegY < 1) or (SegX*SegY > 0x1000) then return end

  local CamX, CamY= R4s(0x14F0,"IWRAM"), R4s(0x14F4,"IWRAM")
  local xp, yp= -(CamX%8), -(CamY%8)

  for y= 0,20 do
    local Top= y*8 + yp
    for x= 0,30 do
      local Left= x*8 + xp
      local xt,yt= (math.floor(CamX/8)+x),(math.floor(CamY/8)+y)
      local Tile= Map[yt*32*SegX + xt]
      if Tile then
        local Fn= Patterns[Tile] or Tile_Default
        Fn(Left,Top,Tile)
      end
    end
  end
end

--*****************************************************************************
local function TileOverlay()
--*****************************************************************************
  local Stage= R4u(  0x152C,"IWRAM")
  if Stage > 16 then
    gui.pixelText(5,5,"Bad Stage")
    return
  end

  local SegmentPtr=   R4u(0x3CD8B0 + 4*Stage,"ROM")
  SegmentPtr= R4u(FetchAddrDomainGBA(SegmentPtr + R4u(0x1530,"IWRAM")*4))
  if (SegmentPtr < ROMmin) or (SegmentPtr >= ROMmax) then
    gui.pixelText(5,5,"Bad Segment index")
    return
  end
  local SegX, SegY= R2s(FetchAddrDomainGBA(SegmentPtr)), R2s(FetchAddrDomainGBA(SegmentPtr+2))
  SegmentPtr= SegmentPtr+4
  if (SegX < 1) or (SegY < 1) or (SegX*SegY > 0x1000) then
    gui.pixelText(5,5,"Bad Segment (wait, how?)")
    return
  end

  local BlockSetPtr= R4u(0x3CD86C + 4*Stage,"ROM")
  local CamX,CamY= R4s(0x14F0,"IWRAM"),R4s(0x14F4,"IWRAM")
  local xp,yp= -(CamX%8), -(CamY%8)

--I will do things inefficiently: For every tile, ask what block.
  for y= 0,20 do
    local Top= y*8 + yp
    for x= 0,30 do
      local Left= x*8 + xp

--PaintBlock
      local xx,yy= math.floor((CamX + x*8)/256), math.floor((CamY + y*8)/256)
      if (xx < SegX) and (yy < SegY) then
        local BlockIndex= R2u(FetchAddrDomainGBA(SegmentPtr + (yy*SegX + xx)*2))
        local Block= R4u(FetchAddrDomainGBA(BlockSetPtr + BlockIndex*4))

        local xt,yt= (math.floor(CamX/8)+x)%32,(math.floor(CamY/8)+y)%32
        local Tile= R1u(FetchAddrDomainGBA(Block + 2 + xt + yt*32))
        local Fn= Patterns[Tile] or Tile_Default
        Fn(Left,Top,Tile)
      end
    end
  end
end

--*****************************************************************************
while true do
--*****************************************************************************
--Our overhead.
  TileOverlay2()

  emu.frameadvance()
end