fceux/output/luaScripts/Gradius-BulletHell.lua

440 lines
11 KiB
Lua

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