SCRIPT_TITLE = "Gradius - Bullet Hell" SCRIPT_VERSION = "1.0" require "m_utils" m_require("m_utils",0) --[[ Gradius - Bullet Hell version 1.0 by miau Visit http://morphcat.de/lua/ for the most recent version and other scripts. Controls Press select to fire a bomb that will destroy all enemy bullets and grant you invincibility for a short period of time. Supported roms Gradius (J), Gradius (U), Gradius (E) Known bugs - dying from blue bullets doesn't trigger the death sound effect -]] --configurable vars local BOMBS_PER_LIFE = 5 local HITBOX = {-2, 4, 9, 9} --vic viper's hit box for collision detection with blue bullets -------------------------------------------------------------------------------------------- local MAX_EXSPR = 64 local timer = 0 local spr = {} local exspr = {} local exsprdata = {} local deathtimer = 0 local bombtimer = 0 local paused = 0 local bombs = BOMBS_PER_LIFE local bulletimg = "" function makebinstr(t) local str = "" for i,v in ipairs(t) do str = str..string.char(v) end return str end function initialize() --Transparency doesn't seem to work for gd images, bummer! bulletimg = makebinstr( { 0xff, 0xfe, 0, 0x6, 0, 0x6, 0x1, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2, 0x33, 0x6e, 0, 0x2, 0x33, 0x6e, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2, 0x33, 0x6e, 0, 0x4d, 0xb6, 0xff, 0, 0xdb, 0xff, 0xfc, 0, 0x2, 0x33, 0x6e, 0, 0, 0, 0, 0, 0x2, 0x33, 0x6e, 0, 0x4d, 0xb6, 0xff, 0, 0x4d, 0xb6, 0xff, 0, 0xdb, 0xff, 0xfc, 0, 0xdb, 0xff, 0xfc, 0, 0x2, 0x33, 0x6e, 0, 0x2, 0x33, 0x6e, 0, 0xdb, 0xff, 0xfc, 0, 0xdb, 0xff, 0xfc, 0, 0x4d, 0xb6, 0xff, 0, 0x4d, 0xb6, 0xff, 0, 0x2, 0x33, 0x6e, 0, 0, 0, 0, 0, 0x2, 0x33, 0x6e, 0, 0xdb, 0xff, 0xfc, 0, 0x4d, 0xb6, 0xff, 0, 0x2, 0x33, 0x6e, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2, 0x33, 0x6e, 0, 0x2, 0x33, 0x6e, 0, 0, 0, 0, 0, 0, 0, 0 } ) for i=0,31 do spr[i] = { status=0, id=0, x=0, y=0, timer=-1, } end end function createexsprite(s) for i=0,MAX_EXSPR-1 do if(exspr[i]==nil) then exspr[i]=s if(exspr[i].id==nil) then exspr[i].id=0 end if(exspr[i].x==nil) then exspr[i].x=0 end if(exspr[i].y==nil) then exspr[i].y=0 end if(exspr[i].vx==nil) then exspr[i].vx=0 end if(exspr[i].vy==nil) then exspr[i].vy=0 end if(exspr[i].timer==nil) then exspr[i].timer=0 end if(exspr[i].ai==nil) then exspr[i].ai="" end return exspr[i] end end return nil end function destroyallexsprites() exspr = {} end function destroyexsprite(i) exspr[i] = nil end function destroysprite(i) memory.writebyte(0x100+i,0x00) memory.writebyte(0x120+i,0x00) memory.writebyte(0x300+i,0x00) end function drawexsprite(id,x,y) gui.gdoverlay(x, y, bulletimg) end function getvicdistance(x,y) return getdistance(vic.x+4,vic.y+8,x,y) end function doexspriteai(s) if(s.ai=="Stray") then if(s.timer==0) then s.vx = -3 s.vy = AND(timer,0x0F) / 8 - 1 end elseif(s.ai=="AimOnce") then if(s.timer==0) then s.vx,s.vy = getvdir(s.x,s.y, vic.x,vic.y) s.vx = s.vx * 2 s.vy = s.vy * 2 --s.vy = AND(timer,0x0F) / 8 - 1 end elseif(s.ai=="AimDelayed") then if(s.timer>=30 and s.timer<=33) then local x,y = getvdir(s.x,s.y, vic.x,vic.y) s.vx = (x+s.vx) s.vy = (y+s.vy) end elseif(s.ai=="AimRight") then if(s.timer==0) then s.vx,s.vy = getvdir(s.x,s.y, vic.x,vic.y) s.vx = s.vx * 2.5 s.vy = s.vy * 1.5 end elseif(s.ai=="AimLeft") then if(s.timer==0) then s.vx,s.vy = getvdir(s.x,s.y, vic.x,vic.y) s.vx = s.vx * 1.5 s.vy = s.vy * 2.5 end elseif(s.ai=="BossPattern1") then if(s.timer==0) then s.vx = math.random(-100,0)/50 s.vy = math.random(-100,0)/70 elseif(s.timer>=30 and s.timer <=35) then s.vx=s.vx/1.1 s.vy=s.vy/1.1 elseif(s.timer==70) then local x,y = getvdir(s.x,s.y, vic.x,vic.y) s.vx = x*3 s.vy = y*3 end elseif(s.ai=="LimitedLifeSpan") then if(s.timer>120) then s.x=999 --results in death end end if(s.timer>600) then --delete extended sprites after 10 seconds in case one gets stuck on screen s.x=999 end end function doboss1ai(s) if(s.timer>80 and s.timer<320 and AND(s.timer,63)==0) then createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) createexsprite({x=s.x,y=s.y,ai="BossPattern1"}) end if(s.timer>320 and s.timer<520) then --pattern2 a if(math.mod(s.timer,27)<=9 and AND(s.timer,3)==3) then createexsprite({x=s.x,y=s.y,vx=-0.2,ai="AimDelayed"}) end end if(s.timer>320 and s.timer<520) then --pattern2 b if(AND(s.timer,63)==0) then createexsprite({x=s.x,y=s.y,vx=-0.7,vy=-1.6}) createexsprite({x=s.x,y=s.y,vx=-1.1,vy=-0.8}) createexsprite({x=s.x,y=s.y,vx=-1.2,vy=-0}) createexsprite({x=s.x,y=s.y,vx=-1.1,vy=0.8}) createexsprite({x=s.x,y=s.y,vx=-0.7,vy=1.6}) end elseif(s.timer>520) then s.timer = 60 end end function dospriteai(s) if(s.id==0x85) then --Fan if(s.timer==30) then createexsprite({x=s.x,y=s.y,ai="Stray"}) end elseif(s.id==0x86) then --Jumper if(AND(s.timer,127)==40) then createexsprite({x=s.x,y=s.y,vx=-1.5,vy=-0.5}) createexsprite({x=s.x,y=s.y,vx=-1,vy=-1.1}) createexsprite({x=s.x,y=s.y,vx=0,vy=-1.4}) createexsprite({x=s.x,y=s.y,vx=1,vy=-1.1}) createexsprite({x=s.x,y=s.y,vx=1.5,vy=-0.5}) end elseif(s.id==0x88) then --Rugul if(AND(s.timer,63)==30) then createexsprite({x=s.x,y=s.y,ai="Stray"}) end elseif(s.id==0x84 or s.id==0x9C) then --Fose, Uska(?) if(AND(s.timer,63)==30) then createexsprite({x=s.x,y=s.y,vx=0,vy=-2}) elseif(AND(s.timer,63)==60) then createexsprite({x=s.x,y=s.y,vx=0,vy=2}) end elseif(s.id==0x89 or s.id==0x8C) then --Rush if(AND(timer,63)==5) then createexsprite({x=s.x,y=s.y,vx=-1,vy=-1}) end elseif(s.id==0x96) then --Moai elseif(s.id==0x97) then --Mother and Child if(AND(timer,7)==0) then local t = s.timer/15 createexsprite({x=s.x+16,y=s.y+16,vx=math.sin(t)-1,vy=math.cos(t),ai="LimitedLifeSpan"}) end elseif(s.id==0x8B) then --Zabu if(s.timer==5) then createexsprite({x=s.x,y=s.y,vx=0.5,ai="AimDelayed"}) end elseif(s.id==0x92 or s.id==0x93 or s.id==0x87 or s.id==0x91) then --Dee-01, Ducker --if(s.timer==5) then local t = AND(s.timer,127) if(t>60 and t<79 and AND(s.timer,3)==3) then --createexsprite({x=s.x,y=s.y,ai="AimOnce"}) createexsprite({x=s.x,y=s.y,ai="AimOnce"}) end elseif(s.id==0x98) then --boss... doboss1ai(s) end end function updatevars() paused = memory.readbyte(0x0015) if(paused==1) then return end --load original sprites from ram for i=0,31 do --if(i==0 or memory.readbyte(0x0300+i)~=0) then if(memory.readbyte(0x0300+i)~=spr[i].id) then spr[i].timer = 0 end spr[i].status=memory.readbyte(0x0100+i) --1=alive, 2=dead spr[i].id=memory.readbyte(0x0300+i) spr[i].x=memory.readbyte(0x0360+i) spr[i].y=memory.readbyte(0x0320+i) spr[i].timer=spr[i].timer+1 if(spr[i].id==0) then spr[i].timer = 0 else dospriteai(spr[i]) end if(i>3 and bombtimer>0 and getvicdistance(spr[i].x+4,spr[i].y+4)<30) then if(spr[i].id~=0x29 and spr[i].id~=0x01 and spr[i].id~=0x99 and spr[i].id~=0x98 and spr[i].id~=0x94 and spr[i].id~=0x97 and spr[i].id~=0x96 and spr[i].id~=0x1E) then --excluded: hidden bonus (0x29), power ups and moai bullets (0x01), boss (0x99+0x98), tentacle (0x94), mother (0x97), moai (0x96), gate (0x1E) destroysprite(i) end end --end end vic = spr[0] if(deathtimer>0) then if(deathtimer==120) then destroyallexsprites() deathtimer = 0 bombs = BOMBS_PER_LIFE else if(deathtimer==1) then --this is part of what gradius does to kill vic viper... --faster and without the annoying sound effect. who cares! memory.writebyte(0x4C,0x78) memory.writebyte(0x0100,0x02) --status = dead memory.writebyte(0x0160,0x00) memory.writebyte(0x0140,0x00) memory.writebyte(0x1B,0xA0) memory.writebyte(0x0120,0x2D) --chr?-- end deathtimer = deathtimer + 1 end end local jp = joypad.get(1) if(jp.select and jp.select~=last_select and bombs>0) then bombtimer = 1 bombs = bombs - 1 destroyallexsprites() --destroy bullets for i=1,31 do if(spr[i].id<4 and spr[i].id~=1) then destroysprite(i) end end end last_select = jp.select if(last_vic_status~=vic.status) then if(vic.status==2) then --vic died in the original game, start death timer to destroy all extended sprites deathtimer = 1 end end last_vic_status = vic.status --calculations on extended sprites for i=0,MAX_EXSPR-1 do if(exspr[i]~=nil) then if(bombtimer>0 and getvicdistance(exspr[i].x+4,exspr[i].y+4)<25) then destroyexsprite(i) else doexspriteai(exspr[i]) exspr[i].timer = exspr[i].timer + 1 exspr[i].x=exspr[i].x+exspr[i].vx exspr[i].y=exspr[i].y+exspr[i].vy if(exspr[i].x>255 or exspr[i].x<0 or exspr[i].y>255 or exspr[i].y<0) then --destroy exsprite exspr[i]=nil break end --collision check with vic viper if(deathtimer==0 and vic.status==1 and exspr[i].x>=vic.x+HITBOX[1] and exspr[i].x<=vic.x+HITBOX[3] and exspr[i].y>=vic.y+HITBOX[2] and exspr[i].y<=vic.y+HITBOX[4]) then deathtimer = 1 end end end end end function render() --bcbox(vic.x-2, vic.y+4, vic.x+9, vic.y+9, "#ffffff") gui.text(0, 8, string.format("bombs %d",bombs)); --gui.text(0, 8, string.format("Lives %d",memory.readbyte(0x0020))); --gui.text(0, 28, string.format("bombtimer %d",bombtimer)); --[[for i=1,31 do if(spr[i].id~=0) then gui.text(spr[i].x, spr[i].y, string.format("%X",spr[i].id)); end end--]] for i=0,MAX_EXSPR-1 do if(exspr[i]~=nil) then drawexsprite(exspr[i].id,exspr[i].x,exspr[i].y) end end if(bombtimer>0) then if(bombtimer<12) then memory.writebyte(0x11,0xFF) --monochrome screen else memory.writebyte(0x11,0x1E) end bombtimer = bombtimer + 1 for i=0,64 do local x = vic.x+4+math.sin(i)*bombtimer*4 + math.random(0,12) local y = vic.y+8+math.cos(i)*bombtimer*4 + math.random(0,12) local c = 255-bombtimer local cs = string.format("#%02x%02x00",c,c) bcpixel(x,y,cs) bcpixel(x-1,y,cs) bcpixel(x+1,y,cs) bcpixel(x,y+1,cs) bcpixel(x,y-1,cs) x = vic.x+4+math.sin(i)*(20+math.sin(bombtimer/5)) y = vic.y+8+math.cos(i)*(20+math.sin(bombtimer/5)) bcpixel(x,y,cs) bcpixel(x-1,y,cs) bcpixel(x,y-1,cs) end if(bombtimer==180) then bombtimer=0 end end end initialize() vic = spr[0] while(true) do updatevars() render() EMU.frameadvance() timer = timer + 1 end