-- Doom Troopers script for gens/bizhawk for TAS
-- by r57shell
local scrollx = 0
local scrolly = 0
local level = 0
local level_prev = -1
local wblocks = 0
local hblocks = 0
local blocks = nil
local cdata = nil
local invmap = {}
local ru8, ru16, ru32, rs8, rs16, rs32
colors = {
[1] = 0xFFFFFF99, -- wall
[2] = 0xFFFFFF30, -- left
[3] = 0xFFFFFF30, -- right
[6] = 0x0000FF99, -- ceil
[10] = 0x00FF0099, -- exit
[15] = 0x99990099, -- player respawn
[16] = 0xFF000099, -- pit / lava
[17] = 0x00FF0099, -- mercury 2 secret room 1 exit
[18] = 0x00FF0099, -- mercury 2 secret room 1 teleport
[19] = 0x00FF0099, -- mercury 2 secret room 2 teleport
[23] = 0x00FF0099, -- mercury 1 secret room exit
[24] = 0x00FF0099, -- mercury 1 secret room teleport
[57] = 0xFF000099, -- nero spikes
[65] = 0x00FF0099, -- nero secret level
[66] = 0x00FF0099, -- nero secret level exit
}
local ground_color = 0x00FFFF99
local text_color = 0xFFFFFFFF
local obj_text_color = 0x00FF00FF
local obj_color = 0x00FF00FF
local wind_color = 0x0000FF99
local red = 0xFF0000FF
local cached = true
local bad_level = {
[3] = true, -- story
[4] = true, -- character selection
[5] = true, -- character selection 2
[10] = true, -- title
[12] = true, -- planet 1 screen
[19] = true, -- planet 2 screen
[26] = true, -- planet 3 screen
[33] = true, -- planet 4 screen
[40] = true, -- victory
[41] = true, -- credits
[47] = true, -- rights
[52] = true, -- sega
[53] = true, -- RE
[54] = true, -- AE
}
local gui_text, gui_box, gui_line, gui_pixel
if gens then
ru8 = memory.readbyteunsigned
ru16 = memory.readwordunsigned
ru32 = memory.readlongunsigned
rs8 = memory.readbytesigned
rs16 = memory.readwordsigned
rs32 = memory.readlongsigned
gui_text, gui_line, gui_pixel = gui.text, gui.line, gui.setpixel
local gb = gui.box
function gui_box(x1, y1, x2, y2, c, o)
gb(x1, y1, x2, y2, o, c)
end
else
ru8 = memory.read_u8
ru16 = memory.read_u16_be
ru32 = memory.read_u32_be
rs8 = memory.read_s8
rs16 = memory.read_s16_be
rs32 = memory.read_s32_be
gui_text = gui.pixelText
gui_box = gui.drawBox
gui_line = gui.drawLine
gui_pixel = gui.drawPixel
function cconvert(c)
if type(c) == "string" then
c = tonumber(c,16)
end
return bit.band(c, 0xFFFFFF00) / 0x100 + bit.band(c, 0xFF) * 0x1000000
end
for i = 0, 0xFF do
if colors[i] then
colors[i] = cconvert(colors[i])
end
end
ground_color = cconvert(ground_color)
text_color = cconvert(text_color)
obj_text_color = cconvert(obj_text_color)
obj_color = cconvert(obj_color)
wind_color = cconvert(wind_color)
red = cconvert(red)
end
local max, min, floor = math.max, math.min, math.floor
gui_pixel_, gui_box_, gui_line_ = gui_pixel, gui_box, gui_line
local func_chunk =
[[local gui_box, gui_line, gui_pixel = gui_box_, gui_line_, gui_pixel_
return function (x,y)
]]
-- splits tile into axis aligned rectangles of same color
-- w - width, h - height, data - actual tile colors. nil ignored.
-- returns list of rectangles.
-- all rectangles defined by: type, x1, y1, x2, y2, color
-- type is rectangle color, type should be ignored
-- x1, y1 - minimum x, y inclusive
-- x2, y2 - maximum x, y inclusive
function tile_split_simple(data, w, h)
local arr = {}
for x = 0, w-1 do
for y = 0, h-1 do
local v = data[y*w+x]
if v then
local t,x1,y1,x2,y2,z = unpack(arr[#arr] or {})
if z == v
and t < 2
and x2 == x
and y2 == y-1 then
arr[#arr][1] = 1
arr[#arr][5] = y
local tt,xx1,yy1,xx2,yy2,zz = unpack(arr[#arr-1] or {})
if (tt == 1 or tt == 2)
and zz == z
and yy1 == y1
and yy2 == y
and xx2 == x-1 then
arr[#arr] = nil
arr[#arr][1] = 2
arr[#arr][4] = x
end
else
arr[#arr+1] = {0,x,y,x,y,v}
end
end
end
end
return arr
end
-- flips tile data and splitter list vertically
function tile_splitter_vflip(data, w, h, list)
n = {}
for y = 0, h-1 do
for x = 0, w-1 do
n[(h-1-y)*w+x] = data[y*w+x]
end
end
for i = 1, #list do
local a = list[i]
a[3], a[5] = h-1-a[5], h-1-a[3]
end
return n
end
-- flips tile data and splitter list diagonally
function tile_splitter_dflip(data, w, h, list)
n = {}
for y = 0, h-1 do
for x = 0, w-1 do
n[x*h+y] = data[y*w+x]
end
end
for i = 1, #list do
local a = list[i]
a[2], a[3], a[4], a[5] = a[3], a[2], a[5], a[4]
end
return n
end
-- adds pixel to the tile
function tile_splitter_add(self, x, y, color)
if color then
table.insert(self, x)
table.insert(self, y)
table.insert(self, color)
end
end
function tile_splitter_split(self)
if #self < 3 then return {} end
-- find bound box
local minx, maxx, miny, maxy = self[1], self[1], self[2], self[2]
for i = 4, #self, 3 do
local x, y = unpack(self, i, i+1)
minx = min(minx, x)
maxx = max(maxx, x)
miny = min(miny, y)
maxy = max(maxy, y)
end
-- crop bounding box
maxx = maxx + 1
maxy = maxy + 1
local w, h = maxx-minx, maxy-miny
data = {}
for i = 1, #self, 3 do
local x, y, color = unpack(self, i, i+2)
data[(y-miny)*w+(x-minx)] = color
end
-- try all scan orders and pick the best
local best = nil
for i = 1, 4 do
local list = tile_split_simple(data, w, h)
if best == nil or #list < #best then
best = list
end
data = tile_splitter_vflip(data, w, h, best)
list = tile_split_simple(data, w, h)
if best == nil or #list < #best then
best = list
end
data = tile_splitter_dflip(data, w, h, best)
w, h = h, w -- swap w, h because we flipped it diagonaly
end
-- shift it back to original position
for i = 1, #best do
best[i][2] = best[i][2] + minx
best[i][3] = best[i][3] + miny
best[i][4] = best[i][4] + minx
best[i][5] = best[i][5] + miny
end
return best
end
tile_splitter_metatable = {
__index = {
add = tile_splitter_add,
split = tile_splitter_split,
},
}
function tile_splitter()
local self = {}
setmetatable(self, tile_splitter_metatable)
return self
end
-- converts list returned by splitter into function code.
function tile_generator(arr)
f = ""
for i = 1, #arr do
local t,x1,y1,x2,y2,z = unpack(arr[i])
if x1 == x2 and y1 == y2 then
f = f.."gui_pixel(x+"..x2..",y+"..y2..","..z..")\n"
elseif x1 == x2 or y1 == y2 then
f = f.."gui_line(x+"..x1..",y+"..y1..
",x+"..x2..",y+"..y2..","..z..")\n"
else
f = f.."gui_box(x+"..x1..",y+"..y1..
",x+"..x2..",y+"..y2..","..z..","..z..")\n"
end
end
return f
end
function climb(s)
local ts = tile_splitter()
for x = 0, 15 do
for y = 0, 15 do
if math.abs(x - s) + math.abs(y) < 10
or x == 0 or x == 15 or y == 0 or y == 15 then
ts:add(x, y, colors[1])
end
end
end
local arr = ts:split()
local f = tile_generator(arr)
f = func_chunk..f.."end"
return loadstring(f)()
end
function climb_wall(s)
local ts = tile_splitter()
for x = 0, 15 do
for y = 0, 15 do
if math.abs(x - s) < 1
or y - floor(y / 3) * 3 == 0 and math.abs(x - s) < 3 then
ts:add(x, y, colors[1])
end
end
end
local arr = ts:split()
local f = tile_generator(arr)
f = func_chunk..f.."end"
return loadstring(f)()
end
function flowf(dx, dy)
local len = math.sqrt(dx * dx + dy * dy)
local nx, ny = dx / len, dy / len
dx = dx * 1
dy = dy * 1
dx = dx + 8
dy = dy + 8
local al = 2
local dx1, dy1 = dx - (nx - ny) * al, dy - (ny + nx) * al
local dx2, dy2 = dx - (nx + ny) * al, dy - (ny - nx) * al
local c = wind_color
return function(x, y)
gui_line(x + 8, y + 8, x + dx, y + dy, c)
gui_line(x + dx, y + dy, x + dx1, y + dy1, c)
gui_line(x + dx, y + dy, x + dx2, y + dy2, c)
gui_box(x, y, x + 15, y + 15, c, 0)
end
end
local climb_wall_r = climb_wall(0)
local climb_wall_l = climb_wall(15)
local callbacks = {
[4] = function (x, y) end,
[5] = function (x, y) end,
[7] = function(x, y) climb_wall_r(x, y) climb_wall_l(x, y) end,
[11] = climb(0), -- climb right
[12] = climb(15), -- climb left
[13] = climb_wall_r, -- climb wall right
[14] = climb_wall_l, -- climb wall left
[0x19] = flowf(-1, 0),
[0x1A] = flowf(-2, 0),
[0x1B] = flowf(-6, 0),
[0x1D] = flowf(1, 0),
[0x1E] = flowf(2, 0),
[0x24] = flowf(0, -14),
[0x25] = flowf(1, -1),
[0x26] = flowf(2, -6),
[0x27] = flowf(6, -10),
[0x28] = flowf(8, -13),
[0x29] = flowf(-1, -1),
[0x2A] = flowf(-2, -6),
[0x2B] = flowf(-6, -10),
[0x2C] = flowf(-8, -13),
[0x30] = flowf(0, 13),
[0x33] = flowf(6, 10),
[0x34] = flowf(8, 13),
[0x35] = flowf(-1, 1),
[0x36] = flowf(-2, 6),
}
for i = 0, 0xFF do
if colors[i] and not callbacks[i] then
local c = colors[i]
callbacks[i] = function (x, y) gui_box(x, y, x + 15, y + 15, c, c) end
end
end
function loadlevel()
wblocks = floor(ru16(0xFF00C0) / 16)
hblocks = floor(ru16(0xFF00C2) / 16)
if wblocks == 0 or hblocks == 0 then
level_prev = -1
return
end
level_prev = level
--local offs = 0x54FEC + level * 2
--offs = offs + ru16(offs)
cdata = {}
blocks = {}
invmap = {}
local BC = ru32(0xFF00BC)
local B4 = ru32(0xFF00B4)
local B8 = ru32(0xFF00B8)
for y = 0, hblocks - 1 do
for x = 0, wblocks - 1 do
local yy = ru16(BC + y * 2) + x * 2
local aa = yy - floor(yy / 0x10000) * 0x10000 + floor(BC / 0x10000) * 0x10000
local a = B4 + floor(ru16(aa) / 2)
local t = ru8(a + 2)
invmap[aa] = {x * 16, y * 16, t}
local dd = ru16(a)
local b8o = ru16(0xFF00C4 + floor(dd / 4))
local b8d = B8 + b8o
if cached then
blocks[y * wblocks + x + 1] = {t, b8d}
end
if cdata[b8d] == nil then
local ts = tile_splitter()
for xxx = 0, 15 do
local v = ru8(b8d + xxx)
if v > 0 then
ts:add(xxx, v - 1, ground_color)
end
end
local arr = ts:split()
local f = tile_generator(arr)
f = func_chunk..f.."end"
cdata[b8d] = loadstring(f)()
end
end
end
end
function drawcell(x, y, t)
local c = callbacks[t]
if c then
c(x - scrollx, y - scrolly)
else
gui_text(x + 6 - scrollx, y + 5 - scrolly, string.format("%X", t), text_color)
end
end
function drawcell_high(x, y, t)
local c = callbacks[t]
if c then
c(x - scrollx, y - scrolly)
c(x - scrollx, y - scrolly)
c(x - scrollx, y - scrolly)
else
gui_text(x + 6 - scrollx, y + 5 - scrolly, string.format("%X", t), text_color)
end
end
function drawcollision()
if level_prev ~= level then
loadlevel()
if level_prev ~= level then
return
end
end
local BC = ru32(0xFF00BC)
local BCC = floor(BC / 0x10000) * 0x10000
local B4 = ru32(0xFF00B4)
local B8 = ru32(0xFF00B8)
local left, right, top, bottom = scrollx, scrollx + 320, scrolly, scrolly + 224
if cached then
for y_ = max(0,floor(top/16)), min(hblocks-1,floor((bottom-1)/16)) do
local y = y_ * 16
for x_ = max(0,floor(left/16)), min(wblocks-1,floor((right-1)/16)) do
local tt = blocks[y_ * wblocks + x_ + 1]
if tt then
local x = x_ * 16
local t, b = unpack(tt)
if t > 0 then
drawcell(x, y, t)
end
local f = cdata[b]
if f then
f(x - scrollx, y - scrolly)
end
end
end
end
else
for y_ = max(0,floor(top/16)), min(hblocks-1,floor((bottom-1)/16)) do
local y = y_ * 16
local y1 = ru16(BC + y_ * 2)
for x_ = max(0,floor(left/16)), min(wblocks-1,floor((right-1)/16)) do
local x = x_*16
local yy = y1 + x_ * 2
local aa = yy - floor(yy / 0x10000) * 0x10000 + BCC
local a = B4 + floor(ru16(aa) / 2)
local t = ru8(a + 2)
local idx = ru16(a)
local b8o = ru16(0xFF00C4 + floor(idx / 4))
local b8d = B8 + b8o
local f = cdata[b8d]
if t > 0 then
drawcell(x, y, t)
end
if f then
f(x - scrollx, y - scrolly)
else
for i = 0, 15 do
local v = ru8(b8d + i)
if v > 0 then
gui_pixel(x - scrollx + i, y - scrolly + v - 1, ground_color)
end
end
end
end
end
end
end
function mercury_boss(dx, dy, ax, ay, tx, ty)
if dx < 0 then
if -dx < 2 then
elseif -dx < 20 then
ax, tx = 4, 0x20
else
ax, tx = 0x28, 0x400
end
else
if dx < 2 then
elseif dx < 20 then
ax, tx = 4, -0x20
elseif dx < 40 then
ax, tx = 15, -0x200
else
ax, tx = 0x28, -0x400
end
end
if dy < 0 then
if -dy < 3 then
elseif -dy < 20 then
ay, ty = 4, 0x20
elseif -dy < 40 then
ay, ty = 15, 0x200
else
ay, ty = 0x28, 0x400
end
else
if dy < 3 then
elseif dy < 20 then
ay, ty = 4, -0x20
elseif dy < 40 then
ay, ty = 15, -0x200
else
ay, ty = 0x28, -0x400
end
end
return ax, ay, tx, ty
end
function mercury_drone(x, y, ax, ay, tx, ty, s)
local dx, dy
if x > 0 then
if y > 0 then
dx = x + s
dy = y - s
else
dx = x - s
dy = y - s
end
else
if y > 0 then
dx = x + s
dy = y + s
else
dx = x - s
dy = y + s
end
end
if dx <= 0 then
tx = 0x500
else
tx = -0x500
end
if dy <= 0 then
ty = 0x500
else
ty = -0x500
end
return ax, ay, tx, ty
end
function traj(n, f, rx, ry, xx, yy, ax, ay, sx, sy, tx, ty, ss)
for i = 1, n do
ax, ay, tx, ty = f(floor(rx / 256) - xx, floor(ry / 256) - yy, ax, ay, tx, ty, ss)
if sx == tx then
elseif sx < tx then
sx = sx + ax
if sx > tx then sx = tx end
elseif sx > tx then
sx = sx - ax
if sx < tx then sx = tx end
end
if sy == ty then
elseif sy < ty then
sy = sy + ay
if sy > ty then sy = ty end
elseif sy > ty then
sy = sy - ay
if sy < ty then sy = ty end
end
gui_line(rx / 256, ry / 256, (rx + sx) / 256, (ry + sy) / 256, red)
rx = rx + sx
ry = ry + sy
end
end
local function rpos(addr)
local r = ru32(addr)
r = r - floor(r / 0x1000000) * 0x1000000
if r > 0x800000 then
return r - 0x1000000
else
return r
end
end
local mercury_boss_attacks = {'F', 'F', 'D', 'S', 'd'}
function drawobjs()
for i = 0, 100 do
local base = ru16(0xFF2BBA + i*2)
if base == 0 then
break
end
base = base + 0xFF0000
local x = ru16(base + 0x32) - scrollx
local y = ru16(base + 0x34) - scrolly
local hp = rs16(base + 0x32 + 0x4A)
local proc = ru32(base + 0x8C)
if proc == 0x64028 or proc == 0x63FDE then
hp = rs16(base + 0x7C)
gui_text(85, 8, string.format("boss atk:%3d mv:%3d aim:%2d", rs16(base + 0xA4), rs16(base + 0xA0), rs16(base + 0xA8)), obj_text_color)
local atk = ru8(base + 0xA6)
local D, S, F = ' ', ' ', ' '
if atk - floor(atk / 8) * 8 >= 4 then D = 'D' end
if atk - floor(atk / 4) * 4 >= 2 then S = 'S' end
if atk - floor(atk / 2) * 2 >= 1 then F = 'F' end
local pt = ru16(base + 0x7A) -- player target
local na = ru16(base + 0xA2) -- next attack
if na >= 3 then
na = 3
if pt ~= 0 and rs16(0xFF0000 + pt + 0x34) <= rs16(base + 0x34) then
na = 4 -- staff failed
end
end
gui_text(90, 16, string.format("hp:%d %s %s", hp, mercury_boss_attacks[na + 1], D..S..F), obj_text_color)
local xx = rs16(base + 0xAA) - scrollx
local yy = rs16(base + 0xAC) - scrolly
local tsx = rs16(base + 0x5A) + rs16(base + 0x5C)
local tsy = rs16(base + 0x64) + rs16(base + 0x66)
local sx = rs16(base + 0x46)
local sy = rs16(base + 0x48)
local ax = rs16(base + 0x60)
local ay = rs16(base + 0x62)
local rx = rpos(base + 0x4E) - scrollx * 256
local ry = rpos(base + 0x52) - scrolly * 256
traj(rs16(base + 0xA0), mercury_boss, rx, ry, xx, yy, ax, ay, sx, sy, tsx, tsy)
if pt ~= 0 then
local px = rs16(0xFF0000 + pt + 0x32) - scrollx
local py = rs16(0xFF0000 + pt + 0x34) - scrolly
gui_box(px - 2, py - 2, px + 2, py + 2, red, 0)
end
gui_box(xx - 1, yy - 1, xx + 1, yy + 1, red, red)
gui_line(x, y, xx, yy, red)
end
if proc == 0x64820 or proc == 0x647E0 then
gui_text(140, 16, string.format("b:%3d mv:%2d", rs16(base + 0xAA), rs16(base + 0xAC)), obj_text_color)
local xx = ru16(base + 0xA6) - scrollx
local yy = ru16(base + 0xA8) - scrolly
local r = ru16(base + 0xA4)
local tsx = rs16(base + 0x5A) + rs16(base + 0x5C)
local tsy = rs16(base + 0x64) + rs16(base + 0x66)
local sx = rs16(base + 0x46)
local sy = rs16(base + 0x48)
local ax = rs16(base + 0x60)
local ay = rs16(base + 0x62)
local rx = rpos(base + 0x4E) - scrollx * 256
local ry = rpos(base + 0x52) - scrolly * 256
traj(rs16(base + 0xAC), mercury_drone, rx, ry, xx, yy, ax, ay, sx, sy, tsx, tsy, r)
if r == 0 then r = 1 end
gui_box(xx - r, yy - r, xx + r, yy + r, red, 0)
gui_line(x, y, xx, yy, red)
end
if x + 16 >= 0 and x < 320
and y + 16 >= 0 and y < 224 then
gui_box(x - 1, y - 1, x + 1, y + 1, obj_color)
if hp > 0 then
gui_text(x - 1, y - 10, string.format("%d", hp), obj_text_color)
end
local z = ru8(base + 0x40)
if ru32(base + 0x68) == 0x51412
and z - floor(z / 128) * 128 >= 64 then
local width = ru8(base + 0x44)
local height = ru8(base + 0x45)
if height > 0x80 then height = height - 0x100 end
gui_box(x - width, y + height, x + width, y, obj_color, 0)
end
end
end
end
function speed(v)
local vv
local r = ' '
if v < 0 then
r = '-'
v = -v
end
vv = floor(v / 256)
return r..string.format("%d.%02d", vv, floor((v - vv * 256) * 100 / 256))
end
function player_text(player, posx, posy)
local xpos = ru16(player + 0x32 + 0x0)
local ypos = ru16(player + 0x32 + 0x2)
local xspeed = rs16(player + 0x46)
local yspeed = rs16(player + 0x48)
local hp = rs16(player + 0x32 + 0x4A)
local wt, wb
if posx < 100 then
wt = ru8(0xFF0048 + 0x23)
wb = ru16(0xFF0048 + 0xE)
else
wt = ru8(0xFF006E + 0x23)
wb = ru16(0xFF006E + 0xE)
end
local text = string.format("x:%6d y:%6d sx:%s sy:%s", xpos, ypos, speed(xspeed), speed(yspeed))
gui_text(posx, posy, text)
text = string.format("hp: %d w:%d b:%d", hp, wt, wb)
gui_text(posx, posy + 8, text)
end
function render()
level = ru16(0xFF002C)
if bad_level[level] then
level_prev = -1
return
end
local player1 = 0xFF0642
local player2 = ru16(0xFF0078) --0xFF06F0
player_text(player1, 20, 0)
if player2 > 0 then
player_text(0xFF0000+player2, 195, 0)
end
scrollx = rs16(0xFF2DEE)
scrolly = rs16(0xFF2DF0)
drawcollision()
drawobjs()
end
function cell_check(a)
if invmap[a] then
local x, y, t = unpack(invmap[a])
if t > 0 then
drawcell_high(x, y, t)
else
--gui_box(x - scrollx, y - scrolly, x + 15 - scrollx, y + 15 - scrolly, obj_color)
end
end
end
if gens then
memory.registerexec(0x5132C,function ()
cell_check(memory.getregister("a1"))
end)
memory.registerexec(0x509C0,function ()
cell_check(memory.getregister("a1"))
end)
gui.register(render)
else
event.onmemoryexecute(function ()
cell_check(emu.getregister("M68K A1"))
end, 0x5132C)
event.onmemoryexecute(function ()
cell_check(emu.getregister("M68K A1"))
end, 0x509C0)
event.onframeend(render)
end