522 lines
16 KiB
Lua
522 lines
16 KiB
Lua
-- Gargoyles, Genesis (BizHawk)
|
|
-- feos, 2015-2017
|
|
|
|
--== Shortcuts ==--
|
|
local rb = memory.read_u8
|
|
local rw = memory.read_u16_be
|
|
local rws = memory.read_s16_be
|
|
local r24 = memory.read_u24_be
|
|
local rl = memory.read_u32_be
|
|
local box = gui.drawBox
|
|
local text = gui.text
|
|
local ptext = gui.pixelText
|
|
local line = gui.drawLine
|
|
local AND = bit.band
|
|
local SHIFT = bit.rshift
|
|
|
|
--== RAM addresses ==--
|
|
local levnum = 0xff00ba
|
|
local LevelFlr = 0xff00c0
|
|
local LevelCon = 0xff00c4
|
|
local mapline_tab = 0xff0244
|
|
local GlobalBase = 0xff1c76
|
|
local GolBase = 0xff2c76
|
|
local MapA_Buff = 0xff4af0
|
|
|
|
--== Camera Hack ==--
|
|
local camhack = false
|
|
local div = 1 -- scale
|
|
local size = 16/div -- block size
|
|
|
|
--== Block cache ==--
|
|
local col = 0 -- block color
|
|
local opout = 0x33000000 -- outer opacity
|
|
local opin = 0x66000000 -- inner opacity
|
|
local op = 0xff000000
|
|
local cache = {}
|
|
|
|
--== Other stuff ==--
|
|
local MsgCutoff = 20
|
|
local MsgTime = 1
|
|
local MsgStep = 14
|
|
local MsgOffs = 2
|
|
local MsgTable = {}
|
|
local XposLast = 0
|
|
local YposLast = 0
|
|
local room = 0
|
|
local workinglast = 0
|
|
local wSize = client.getwindowsize()
|
|
local lagcount = emu.lagcount()
|
|
gui.defaultTextBackground(0xff000000)
|
|
|
|
--== Object types ==--
|
|
local types = {
|
|
"Goliath","Orb","Health1PLR","Health2PLR","Health1NME","Health2NME","Numeral",
|
|
"BigExplode","SmallExplode","GlassDebris","MetalDebris","WoodDebris",
|
|
"WallDebris","SignPiece","SteamVent","BreakWall","SkyLight","BreakLight",
|
|
"ThrowCrate","BreakEdgeLeft","BreakEdgeRight","Spark","Spark2","Sparks",
|
|
"Sparks2","Fireball","HomingProj1","HorzProj1","VertProj1","DirProj1",
|
|
"DirProj2","DropMine","Scratch","Icon","RaptorBot","SniperBot","SpiderBot",
|
|
"WaspBot","Xanatos","PlasmaBot","RabidHH","MorningStar","Archer","Arrow",
|
|
"Valkyrie","Axe","WeaponExp","Couldron","SpittingCouldron","FireballHead",
|
|
"FireballTrail","BigFireballHead","BigFireballTrail","Oil","OilGenerator",
|
|
"Claw","Stump","StumpBubble","StumpFire","ClawStump","StumpFireGen","Vent",
|
|
"VentSparks","Chain","FlameLick","Floor","MutVikBody","MutVikHead",
|
|
"MutVikHammer","EyeOfOdin","EyeOfOdinTrail","L1BreakWall","Catapult",
|
|
"L1BreakFloor","Gate","GateCrusher","Weight","WeightCrusher","WallFire",
|
|
"Balista","BalistaLog","PasteWall","FlameBoulder","CastlePiece",
|
|
"MutantSpiderBot","MutSpiLegs","MutSpiHead","MutSpiHeadFlame","MutSpiProj",
|
|
"MutSpiElecV","MutSpiElecH","PlasmaBall","PlasmaBallTail","PlasmaDeadHead",
|
|
"VertFlame","WallFlame","FloorFlame","OPPlatform","OPLink","OPOrb",
|
|
"Furnace","RobotGenerator","RockGenerator","BigRock","MediumRock",
|
|
"SmallRock","BigCouldronGen","BigCouldron","Trough","TroughGen","Energizer",
|
|
"Demona","TrajectoryProj","WallPaste","EdgePaste","Tentacle","Infuser",
|
|
"BigGuns","BigGunsProj","HighSignPole","HighSign","LowLight","L5Skylight",
|
|
"L5Wall","ElecGenerator","Electricity","WaspGenerator","TunnelEdge",
|
|
"ForegroundPost","Sorcerer","LightningTop","LightningBot","MDemonaWallFire",
|
|
"MDemonaFloorFire","EyeRooftopUp","EyeRooftopDn","EyeRaptor"
|
|
}
|
|
|
|
local function RoomTime()
|
|
local start11 = 894--767
|
|
local start12 = 2294
|
|
local start13 = 5468 -- 4254 -- 4101
|
|
local startl4 = 5506
|
|
local startl5 = 7117
|
|
local startl6 = 8412
|
|
local startl7 = 17117
|
|
local timer = emu.framecount()
|
|
if timer < start11 then room = timer
|
|
elseif timer < start12 then room = timer - start11
|
|
elseif timer < start13 then room = timer - start12
|
|
elseif timer < startl4 then room = timer - start13
|
|
elseif timer < startl5 then room = timer - startl4
|
|
elseif timer < startl6 then room = timer - startl5
|
|
elseif timer < startl7 then room = timer - startl6
|
|
end
|
|
text(2, 2, string.format("cx:%5d\ncy:%5d\nroom:%d", camx, camy, room), "white", "bottomright")
|
|
end
|
|
|
|
local function HUD()
|
|
--if working > 0 then return end
|
|
local rndcol = "white"
|
|
if rndlast ~= rnd1 then rndcol = "red" end
|
|
text(0, 2, string.format("RNG:%08X %04X", rnd1, rnd2), rndcol, "bottomleft")
|
|
text(2, 0, string.format(
|
|
"x: %4d\ny: %4d\ndx: %3d\ndy: %3d\nhp: %3d\nrun:%3d\ninv:%3d",
|
|
Xpos, Ypos, Xspd, Yspd, health, run, inv),
|
|
"white", "topright")
|
|
end
|
|
|
|
local function CamhackHUD()
|
|
if working == 0 then
|
|
-- screen edge
|
|
box((backx-camx- 1)/div,
|
|
(backy-camy- 1)/div,
|
|
(backx-camx+320)/div,
|
|
(backy-camy+224)/div,
|
|
0xff0000ff, 0)
|
|
-- map edge
|
|
box( 0-camx/div+size,
|
|
0-camy/div+size,
|
|
mapw/div-camx/div,
|
|
maph/div-camy/div,
|
|
0xff0000ff, 0)
|
|
end
|
|
if camhack or div > 1 then
|
|
text(0, 0, string.format("div:%d", div), "white", "topleft")
|
|
end
|
|
end
|
|
|
|
local function PosToIndex(x, y)
|
|
return math.floor(x/16)+math.floor(y/16)*xblocks
|
|
end
|
|
|
|
local function IndexToPos(i)
|
|
return { x=(i%xblocks)*16, y=math.floor(i/xblocks)*16 }
|
|
end
|
|
|
|
local function InBounds(x, minimum, maximum)
|
|
if x >= minimum and x <= maximum
|
|
then return true
|
|
else return false
|
|
end
|
|
end
|
|
|
|
local function GetBlock(x, y)
|
|
if working > 0 then return nil end
|
|
local final = { contour={}, block=0 }
|
|
if x > 0 and x < mapw
|
|
and y > 0 and y < maph then
|
|
local pixels = 0
|
|
local x1 = x/div-camx/div
|
|
local x2 = x1+size-1
|
|
local y1 = y/div-camy/div
|
|
local y2 = y1+size-1
|
|
local d4 = rw(mapline_tab+SHIFT(y, 4)*2)
|
|
local a1 = r24(LevelFlr+1)
|
|
local d1 = SHIFT(rw(MapA_Buff+d4+SHIFT(x, 4)*2), 1)
|
|
final.block = rb(a1+d1+2)
|
|
d1 = rw(a1+d1)
|
|
a1 = r24(LevelCon+1)+d1
|
|
if rb(a1) > 0 or rb(a1+8) > 0 then
|
|
for pixel=0, 15 do
|
|
final.contour[pixel] = rb(a1+pixel)
|
|
end
|
|
else
|
|
final.contour = nil
|
|
end
|
|
else
|
|
return nil
|
|
end
|
|
return final
|
|
end
|
|
|
|
local DrawBlock = {
|
|
[0x80] = function(x1, y1, x2, y2) -- WALL
|
|
col = 0x00ffffff -- white
|
|
line(x1, y1, x1, y2, col+op) -- left
|
|
line(x2, y1, x2, y2, col+op) -- right
|
|
end,
|
|
[0x81] = function(x1, y1, x2, y2) -- CEILING
|
|
col = 0x00ffffff -- white
|
|
line(x1, y2, x2, y2, col+op) -- bottom
|
|
end,
|
|
[0x82] = function(x1, y1, x2, y2) -- CLIMB_U
|
|
col = 0x0000ffff -- cyan
|
|
line(x1, y2, x2, y2, col+op) -- bottom
|
|
end,
|
|
[0x83] = function(x1, y1, x2, y2) -- CLIMB_R
|
|
col = 0x0000ffff -- cyan
|
|
line(x1, y1, x1, y2, col+op) -- left
|
|
end,
|
|
[0x84] = function(x1, y1, x2, y2) -- CLIMB_L
|
|
col = 0x0000ffff -- cyan
|
|
line(x2, y1, x2, y2, col+op) -- right
|
|
end,
|
|
[0x85] = function(x1, y1, x2, y2) -- CLIMB_LR
|
|
col = 0x0000ffff -- cyan
|
|
line(x1, y1, x1, y2, col+op) -- left
|
|
line(x2, y1, x2, y2, col+op) -- right
|
|
end,
|
|
[0x86] = function(x1, y1, x2, y2) -- CLIMB_R_STAND_R
|
|
col = 0x00ffffff -- white
|
|
line(x1, y1, x2, y1, col+op) -- top
|
|
col = 0x0000ffff -- cyan
|
|
line(x1, y1, x1, y2, col+op) -- left
|
|
end,
|
|
[0x87] = function(x1, y1, x2, y2) -- CLIMB_L_STAND_L
|
|
col = 0x00ffffff -- white
|
|
line(x1, y1, x2, y1, col+op) -- top
|
|
col = 0x0000ffff -- cyan
|
|
line(x2, y1, x2, y2, col+op) -- right
|
|
end,
|
|
[0x88] = function(x1, y1, x2, y2) -- CLIMB_LR_STAND_LR
|
|
col = 0x00ffffff -- white
|
|
line(x1, y1, x2, y1, col+op) -- top
|
|
col = 0x00ff00ff -- cyan
|
|
line(x1, y1, x1, y2, col+op) -- left
|
|
col = 0x0000ffff -- cyan
|
|
line(x2, y1, x2, y2, col+op) -- right
|
|
end,
|
|
[0x70] = function(x1, y1, x2, y2) -- GRAB_SWING
|
|
col = 0x0000ff00 -- green
|
|
box(x1, y1, x2, y2, col, col+opout)
|
|
end,
|
|
[0x7f] = function(x1, y1, x2, y2) -- EXIT
|
|
col = 0x00ffff00 -- yellow
|
|
end,
|
|
[0xd0] = function(x1, y1, x2, y2) -- SPIKES
|
|
col = 0x00ff0000 -- red
|
|
box(x1, y1, x2, y2, col, col+opout)
|
|
end,
|
|
[0xd1] = function(x1, y1, x2, y2) -- SPIKES
|
|
col = 0x00ff0000 -- red
|
|
box(x1, y1, x2, y2, col, col+opout)
|
|
end
|
|
}
|
|
|
|
local function DrawBlockDefault(x1, y1, x2, y2) -- LEVEL_SPECIFIC
|
|
col = 0x00ff8800 -- orange
|
|
box(x1, y1, x2, y2, col+opin, col+opout)
|
|
end
|
|
|
|
local function DrawBG(unit, x, y)
|
|
local val= 0
|
|
local x1 = x/div-camx/div-(camx%16)/div
|
|
local x2 = x1+size-1
|
|
local y1 = y/div-camy/div-(camy%16)/div
|
|
local y2 = y1+size-1
|
|
if unit.contour ~= nil then
|
|
box(x1, y1, x2, y2, 0x5500ff00, 0x5500ff00)
|
|
for pixel=0, 15 do
|
|
val = unit.contour[pixel]
|
|
--[ [--
|
|
if val > 0 then
|
|
gui.drawPixel(
|
|
x1+pixel/div,
|
|
y1+val/div-1/div,
|
|
0xffffff00)
|
|
end
|
|
--]]--
|
|
end
|
|
end
|
|
if unit.block > 0 then
|
|
local Fn = DrawBlock[unit.block] or DrawBlockDefault
|
|
Fn(x1, y1, x2, y2)
|
|
box(x1, y1, x2, y2, col+opin, col+opout)
|
|
end
|
|
end
|
|
|
|
local function Background()
|
|
if working > 0 then
|
|
cache = {}
|
|
return
|
|
end
|
|
if camhack then
|
|
camx = Xpos-320/2*div
|
|
camy = Ypos-224/2*div
|
|
box(0, 0, 320, 240, 0, 0x66000000)
|
|
end
|
|
local border = 0
|
|
local offset = 32
|
|
local basex = camx+border
|
|
local basey = camy+border
|
|
local basei = PosToIndex(basex-offset, basey-offset)
|
|
local boundx = 320*div-border
|
|
local boundy = 224*div-border
|
|
local xblockstockeck = ((camx+boundx+offset)-(basex-offset))/size/div
|
|
local yblockstockeck = ((camy+boundy+offset)-(basey-offset))/size/div
|
|
for yblock = 0, yblockstockeck do
|
|
for xblock = 0, xblockstockeck do
|
|
local i = yblock*xblocks+xblock+basei
|
|
local x = basex+xblock*size*div
|
|
local y = basey+yblock*size*div
|
|
if InBounds(x, basex-offset, camx+boundx+offset) then
|
|
local unit = cache[i]
|
|
if unit == nil or workinglast > 0 then
|
|
if InBounds(x, basex, camx+boundx)
|
|
and InBounds(y, basey, camy+boundy)
|
|
then cache[i] = GetBlock(x, y)
|
|
end
|
|
else
|
|
if not InBounds(x, basex, camx+boundx)
|
|
and not InBounds(y, basey, camy+boundy)
|
|
then cache[i] = nil
|
|
end
|
|
end
|
|
if unit ~= nil then
|
|
DrawBG(unit, x, y)
|
|
end
|
|
elseif cache[i] ~= nil
|
|
then cache[i] = nil
|
|
end
|
|
end
|
|
end
|
|
CamhackHUD()
|
|
end
|
|
|
|
local function Clamp(v, vmin, vmax)
|
|
if v < vmin then v = vmin end
|
|
if v > vmax then v = vmax end
|
|
return v
|
|
end
|
|
|
|
local function Objects()
|
|
if working > 0 then return end
|
|
for i=0, 63 do
|
|
local base = GlobalBase+i*128
|
|
local flag2 = AND(rb(base+0x49), 0x10) -- active
|
|
if flag2 == 0x10 then
|
|
local xpos = rw (base+0x00)
|
|
local ypos = rw (base+0x02)
|
|
local state = rw (base+0x0c)
|
|
local dmg = rb (base+0x10)
|
|
local id = rw (base+0x40)
|
|
local hp = rw (base+0x50)
|
|
local cRAM = r24(base+0x75) -- pointer to 4 collision boxes per object
|
|
local xscr = (xpos-camx)/div
|
|
local yscr = (ypos-camy)/div
|
|
local num = id/6
|
|
local name = types[num]
|
|
local col = 0 -- collision color
|
|
for boxx=0, 4 do
|
|
local x0 = rw (cRAM+boxx*8)
|
|
local x1 = (rws(cRAM+boxx*8+0)-camx)/div
|
|
local y1 = (rws(cRAM+boxx*8+2)-camy)/div
|
|
local x2 = (rws(cRAM+boxx*8+4)-camx)/div
|
|
local y2 = (rws(cRAM+boxx*8+6)-camy)/div
|
|
if boxx == 0 then
|
|
col = 0xff00ff00 -- body
|
|
-- archer hp doesn't matter
|
|
if id == 282 or id == 258 then hp = 1 end
|
|
if hp > 0 and id > 0 and x0 ~= 0x8888 then
|
|
local xx = Clamp(xscr, 0, 318-string.len(name)*4)
|
|
local yy = Clamp(yscr, 0, 214)
|
|
ptext(xx, yy+2, string.format("%d", hp), col)
|
|
end
|
|
elseif boxx == 1 then
|
|
col = 0xffffff00 -- floor
|
|
elseif boxx == 2 then
|
|
if dmg > 0
|
|
then col = 0xffff0000 -- projectile
|
|
else col = 0xff8800ff -- item
|
|
end
|
|
if dmg > 0 then
|
|
text(x1*wSize+2, y2*wSize+1,
|
|
string.format("%d", dmg), col, 0x88000000)
|
|
end
|
|
else
|
|
col = 0xffffffff -- other
|
|
end
|
|
if x1 ~= 0x8888
|
|
and x1 <= 320 and x2 >= 0
|
|
and y1 <= 224 and y2 >= 0 then
|
|
box(x1, y1, x2, y2, col, 0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function PostRndRoll()
|
|
for i = 1,#MsgTable do
|
|
if MsgTable[i] and MsgTable[i].index == i then
|
|
local base = MsgTable[i].base
|
|
local xpos = rw(base+0x00)
|
|
local ypos = rw(base+0x02)
|
|
local id = rw(base+0x40)
|
|
local x = (xpos-camx)/div
|
|
local y = (ypos-camy)/div
|
|
local num = id/6
|
|
local ymsg = 0
|
|
local yoffs = math.floor((i-1)/MsgCutoff)*14
|
|
local name = types[num]
|
|
local color = 0xffffff00
|
|
|
|
if base == GolBase then
|
|
name = "Goliath"
|
|
elseif not name then
|
|
name = string.format("%X", base)
|
|
color = 0xff00ffff
|
|
end
|
|
if y < 224/2 then
|
|
yoffs = -yoffs
|
|
ymsg = 210
|
|
end
|
|
|
|
x = Clamp(x, 2, 320-string.len(name)*4)
|
|
y = Clamp(y, 20, 214)
|
|
|
|
line ((i-1)%MsgCutoff*MsgStep+3 +MsgOffs, ymsg+yoffs+4, x, y, color-0x88000000)
|
|
ptext((i-1)%MsgCutoff*MsgStep*wSize+MsgOffs, ymsg+yoffs, i, color)
|
|
|
|
MsgTable[i].timer = MsgTable[i].timer-1
|
|
if MsgTable[i].timer <= 0 then
|
|
MsgTable[i] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function PlayerBoxes()
|
|
if working > 0 then return end
|
|
local xx = (Xpos-camx)/div
|
|
local yy = (Ypos-camy)/div
|
|
local col = 0xff00ffff
|
|
local swcol = col -- usual detection
|
|
if Yspd > 0 then -- gimme swings to grab!
|
|
swcol = 0xff00ff00
|
|
elseif Yspd == 0 then -- can tell that too
|
|
swcol = 0xffffffff
|
|
end
|
|
if facing == 2
|
|
then box(xx-0xf /div-2, yy-0x2c/div-1, xx-0xf/div+0, yy-0x2c/div+1, swcol, 0) -- lefttop
|
|
else box(xx+0xf /div , yy-0x2c/div-1, xx+0xf/div+2, yy-0x2c/div+1, swcol, 0) -- rightttop
|
|
end
|
|
box(xx -1, yy-0x2c/div-1, xx +1, yy-0x2c/div+1, col, 0) -- top
|
|
box(xx-0xf /div-2, yy-0x1f/div-1, xx-0xf /div+0, yy-0x1f/div+1, col, 0) -- left
|
|
box(xx+0x10/div-1, yy-0x1f/div-1, xx+0x10/div+1, yy-0x1f/div+1, col, 0) -- right
|
|
-- box(xx -1, yy-0x1f/div-1, xx +1, yy-0x1f/div+1, col, 0) -- center
|
|
box(xx -1, yy-0x0f/div-1, xx +1, yy-0x0f/div+1, col, 0) -- bottom
|
|
box(xx -1, yy -1, xx +1, yy +1,0xffffff00, 0) -- feet
|
|
-- box(xx -1, yy+0x10/div-1, xx +1, yy+0x10/div+1, col, 0) -- ground
|
|
end
|
|
|
|
local function Input()
|
|
local i, u, d, l, r, a, b, c, s
|
|
if movie.isloaded()
|
|
then i = movie.getinput(emu.framecount()-1)
|
|
else i = joypad.getimmediate()
|
|
end
|
|
if i["P1 Up" ] then u = "U" else u = " " end
|
|
if i["P1 Down" ] then d = "D" else d = " " end
|
|
if i["P1 Left" ] then l = "L" else l = " " end
|
|
if i["P1 Right"] then r = "R" else r = " " end
|
|
if i["P1 A" ] then a = "A" else a = " " end
|
|
if i["P1 B" ] then b = "B" else b = " " end
|
|
if i["P1 C" ] then c = "C" else c = " " end
|
|
if i["P1 Start"] then s = "S" else s = " " end
|
|
text(1, 10, u..d..l..r..a..b..c..s, "yellow")
|
|
end
|
|
|
|
event.onframeend(function()
|
|
emu.setislagged(rb(0xfff6d4) == 0)
|
|
if rb(0xfff6d4) == 0 then
|
|
lagcount = lagcount+1
|
|
framecol = "red"
|
|
else
|
|
framecol = "white"
|
|
end
|
|
emu.setlagcount(lagcount)
|
|
wSize = client.getwindowsize()
|
|
rndlast = rnd1
|
|
workinglast = working
|
|
XposLast = Xpos
|
|
YposLast = Ypos
|
|
end)
|
|
|
|
event.onmemoryexecute(function()
|
|
local a0 = AND(emu.getregister("M68K A0"), 0xffffff)
|
|
if a0 ~= 0xff4044 then
|
|
for i = 1, 200 do
|
|
if MsgTable[i] == nil then
|
|
MsgTable[i] = { index = i, timer = MsgTime, base = a0 }
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end, 0x257A, "RNGseed")
|
|
|
|
local function main()
|
|
rnd1 = rl (0xff001c)
|
|
rnd2 = rw (0xff0020)
|
|
working = rb (0xff0073)
|
|
xblocks = rw (0xff00d4)
|
|
mapw = rw (0xff00d4)*8
|
|
maph = rw (0xff00d6)*8
|
|
Xpos = rws(0xff0106)
|
|
Ypos = rws(0xff0108)
|
|
camx = rws(0xff010c)+16
|
|
camy = rws(0xff010e)+16
|
|
run = rb (0xff1699)
|
|
inv = rw (0xff16d2)
|
|
health = rws(0xff2cc6)
|
|
backx = camx
|
|
backy = camy
|
|
Xspd = Xpos-XposLast
|
|
Yspd = Ypos-YposLast
|
|
facing = AND(rb(GolBase+0x48), 2) -- object flag 1
|
|
if working > 0 then MsgTable = {} end
|
|
Background()
|
|
PlayerBoxes()
|
|
Objects()
|
|
PostRndRoll()
|
|
HUD()
|
|
RoomTime()
|
|
end
|
|
|
|
while true do
|
|
main()
|
|
emu.frameadvance()
|
|
end |