-
-
Save realmonster/93090d4ef0b40c1b88d62f8840fad675 to your computer and use it in GitHub Desktop.
Demolition Man script for TAS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Demolition Man 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 indexes = nil | |
local cdata = nil | |
local objs = {} | |
local ru8,ru16,ru32,rs8,rs16,rs32 | |
local colors = { | |
[0x40] = 0xFFFFFF99, -- left wall | |
[0x41] = 0x00FFFF99, -- ground | |
[0x42] = 0xFFFFFF99, -- right wall | |
[0x43] = 0x00FF0099, -- wire ground | |
[0x44] = 0x00990099, -- zip line to the right | |
[0x45] = 0x00999999, -- right close to climb | |
[0x46] = 0x0000FF99, -- ladder left | |
[0x47] = 0x0000FF99, -- ladder right | |
[0x48] = 0xCCCCCC99, -- climb left | |
[0x49] = 0xCCCCCC99, -- climb right | |
[0x4A] = 0x00990099, -- horizontal zip line | |
[0x4B] = 0x00990099, -- zip line to the left | |
[0x4C] = 0x00999999, -- left close to climb | |
[0x4D] = 0x99990099, | |
[0x4E] = 0xCC00CC99, -- obstacle | |
[0x4F] = 0x9900FF99, -- ?? | |
[0x50] = 0xFF400099, | |
[0x52] = 0xFF400099, | |
[0x56] = 0xFF400099, | |
[0x58] = 0xFF400099, | |
[0x5F] = 0xFF400099, | |
} | |
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 | |
for i = 0, 0xFF do | |
if colors[i] then | |
colors[i] = bit.band(colors[i],0xFFFFFF00)/0x100+bit.band(colors[i],0xFF)*0x1000000 | |
end | |
end | |
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 loadlevel() | |
level_prev = level | |
local offs = 0x1AF924+level*0x52 | |
wblocks, hblocks = ru16(ru32(offs+3*4)), ru16(ru32(offs+3*4)+2) | |
local blocks_offs, indexes_offs, ctiles, cdata_offs | |
= ru32(offs+3*4)+4, ru32(offs+2*4), ru32(offs+5*4), ru32(offs+6*4) | |
blocks = {} | |
indexes = {} | |
cdata = {} | |
for y = 0, hblocks-1 do | |
blocks[y] = {} | |
for x = 0, wblocks-1 do | |
local bs = blocks_offs+(y*wblocks+x)*2 | |
local idx = ru16(bs) | |
blocks[y][x] = idx | |
if indexes[idx] == nil then | |
local t = {} | |
local idx1 = indexes_offs+idx*1024 | |
for yy = 0, 15 do | |
t[yy] = {} | |
for xx = 0, 31 do | |
local idx2 = idx1 + (xx+yy*32)*2 | |
local idx3 = ru16(idx2) | |
local idx4 = ru8(ctiles+2+idx3) | |
t[yy][xx] = idx4 | |
if cdata[idx4] == nil then | |
local cd = cdata_offs + 4 + idx4*256 | |
local ts = tile_splitter() | |
for xxx = 0, 15 do | |
for yyy = 0, 15 do | |
local v = ru8(cd+yyy*16+xxx) | |
ts:add(xxx, yyy, colors[v]) | |
end | |
end | |
local arr = ts:split() | |
local f = tile_generator(arr) | |
f = func_chunk..f.."end" | |
cdata[idx4] = loadstring(f)() | |
end -- if | |
end -- for xx | |
end -- for yy | |
indexes[idx] = t | |
end -- if | |
end | |
end | |
local obj = ru32(ru32(offs+11*4)) | |
local objs_count = ru32(ru32(offs+13*4)) | |
objs = {} | |
for i = 0, objs_count-1 do | |
local x = ru16(obj+i*6) | |
local y = ru16(obj+i*6+2) | |
local t = ru16(obj+i*6+4) | |
local f = ru32(0x1FFBAA+t*4) | |
c = 0x00FF00FF | |
cc = 0x00FF0000 | |
if f == 0x1AEDAE then | |
c = 0x0000FFFF | |
cc = 0x0000FF00 | |
end | |
if f == 0x1BC85E then | |
c = 0x00FFFFFF | |
cc = 0x00FFFF00 | |
end | |
if f == 0 then | |
c = 0xFFFFFFFF | |
cc = 0xFFFFFF00 | |
end | |
if not gens then | |
c = bit.band(c,0xFFFFFF00)/0x100+bit.band(c,0xFF)*0x1000000 | |
cc = bit.band(cc,0xFFFFFF00)/0x100+bit.band(cc,0xFF)*0x1000000 | |
end | |
objs[i+1] = {x,y,string.format("%X",t),c,cc} | |
end | |
end | |
function drawcollision() | |
if level > 9 then return end | |
if level_prev ~= level then | |
loadlevel() | |
end | |
local left,right,top,bottom = scrollx,scrollx+320,scrolly,scrolly+224 | |
for y = max(0,floor(top/256)), min(hblocks-1,floor((bottom-1)/256)) do | |
for x = max(0,floor(left/512)), min(wblocks-1,floor((right-1)/512)) do | |
local idx = blocks[y][x] | |
for yy = max(0,floor((top-y*256)/16)), min(15,floor((bottom-1-y*256)/16)) do | |
for xx = max(0,floor((left-x*512)/16)), min(31,floor((right-1-x*512)/16)) do | |
local idx1 = indexes[idx][yy][xx] | |
cdata[idx1](x*512+xx*16-scrollx,y*256+yy*16-scrolly) | |
end | |
end | |
end | |
end | |
end | |
function drawobjs() | |
local struct = 0x1AF924+0x52*level | |
for i = 1, #objs do | |
local x,y,t,c,cc = unpack(objs[i]) | |
if x+16 >= scrollx and x < scrollx+320 | |
and y+16 >= scrolly and y < scrolly+224 then | |
gui_box(x-scrollx,y-scrolly,x+15-scrollx,y+15-scrolly,c) | |
gui_text(x+2-scrollx,y+1-scrolly,t,c) | |
end | |
end | |
end | |
function render() | |
level = ru16(0xFF0538) | |
if level > 9 then return end | |
local player = ru32(0xFF055A) | |
local xpos = ru32(player+0x26) | |
local ypos = ru32(player+0x2A) | |
local hp = rs16(player+0x3C) | |
local granades = ru8(player+0xDE) | |
local granades_count = ru8(player+0xDF) | |
local w = ru8(player+0xE0) | |
local ammo = ru8(player+0xE1) | |
local state = ru16(player+0x4A) | |
local mapa = ru32(player+0x72) | |
local mapb = ru32(player+0x76) | |
if ru32(0x1AF924+level*0x52+5*4) ~= mapa | |
or ru32(0x1AF924+level*0x52+6*4) ~= mapb then | |
return | |
end | |
local text = string.format("%8X %8X %X %d",xpos,ypos,state,hp) | |
text = text..string.format("\n%d %d %d %d",granades,granades_count,w,ammo) | |
gui.text(125,0,text) | |
scrollx = rs16(0xFF04CC) | |
scrolly = rs16(0xFF04CE) | |
drawcollision() | |
drawobjs() | |
end | |
if gens then | |
gui.register(render) | |
else | |
event.onframeend(render) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Further tips:
#arr
is aO(log(#arr))
operation (it does a binary search for a non-nil
value followed by anil
), you'll most probably get better perf by tracking the length manually (not sure how much this matters, I suppose that thecdata[idx1]()
calls dominate here anyways).func_chunk
asthen pass the functions as argument at call time
cdata[idx4] = loadstring(f)(gui_box, gui_line, gui_pixel)
. No need for globals.